April 12, 2025
4 min read

Why I Removed Jinja2 from UiWizard

Why remove the Jinja2 as a dependency? For those unfamiliar, Jinja2 is a fast and powerful templating engine for Python — and it does its job exceptionally well.

When I first created UiWizard, I instinctively went with Jinja2. It was familiar, reliable, and let me get up and running quickly. Initially, I used it to render two core files: default.html and default.js. These served as the foundational templates that every page in the app relied on. And for a while, that worked just fine. When I as rewriting some of the toast functionality i figured out that I did not need to template the default js file. The data that I needed to template could be injected into the default HTML page. Some of the functionality that I want to enable is that the user can set the delay of a toast before it disappears and that should be specified inside python and the user should not know of the underlying Javascript.

But things started to change as I was rewriting some of the toast functionality.

I realized I didn’t actually need to template the default.js file. The dynamic values I needed could instead be injected into the HTML, and the JavaScript could just pick them up from attributes. For example, I wanted users to be able to configure how long a toast message stays visible — purely from Python — without them needing to understand or touch the underlying JavaScript.

The solution? Use a custom HTML attribute to pass JSON data that JavaScript can consume. Here’s how that looks:

toast.attributes["hx-toast-delay"] = json.dumps({"delay": request.app.toast_delay})

This embeds the configuration as a JSON string into the hx-toast-delay attribute. Then in JavaScript, we simply read and parse it:

const toastDelay = JSON.parse(document.getElementById("toast").getAttribute("hx-toast-delay")).delay;

Now that I no longer needed to template the JavaScript file the only template that was left was the default.html file. This file contained the wrapper code for the user produced html code. It was just the standard doctype, header and body elements along with some minor config attributes and the components with CSS and JavaScript.

That made me wonder: What is the overhead of using Jinja2?

Most of the functionality is creating classes that represent elements in the DOM and then rendering those elements as a string for the browser and Jinja was used for the same purpose. So I needed to investigate. For this purpose I used PyInstrument to profile a request to the server to get a better idea of what was going on. Someone had already made a FastAPI middleware that would allow me to profile the endpoint.

A great feature of PyInstrument is that it can output the profile as HTML. This means that I can embed the output into this blog!

The output is the following profile with half of the time of the request is spent on Jinja2 templating. I did also see response times >100ms because of Jinja2 on windows but I do not trust anything on Windows. But in general Jinja was half of the response rendering.

Here is the before (With Jinja2):

Replacing Jinja2

Since UiWizard already generates HTML from Python structures, removing Jinja2 just meant porting the base default.html into Python code — representing it as a list of UiWizard component classes instead of a static template.

Here's the after profile:

The performance gain? About 20ms shaved off each request. That may seem small, but the real win is dropping a dependency and simplifying the stack. Fewer moving parts. Less magic. More control.

Final Thoughts

I’m pretty happy with how it turned out. The framework is slightly faster, the codebase is cleaner, and there’s one less dependency to worry about.

Best of all, this change reinforces the philosophy behind UiWizard: Python-first, expressive, and minimal overhead.

And while this gives more flexibility (in performance) to developers, it also introduces more room to make poor design choices (And hurt performance). But hey — that’s the tradeoff when you give people power.