Every framework wants you to marry its templating engine. Django gives you Django templates. Rails gives you ERB. Next.js gives you React. Hugo gives you Go templates. Learn our syntax, adopt our conventions, live in our world.

note2cms does not have a templating engine. It has a slot.

The Contract

A theme in note2cms is a function. It receives data — title, date, content, tags, reading time. It returns an HTML string. That is the entire interface. What happens between input and output is none of the pipeline’s business.

The default theme ships with React. Server-side rendered via renderToString, compiled from JSX by esbuild, output as pure static HTML. Zero JavaScript reaches the reader’s browser. React is the authoring tool, not the runtime.

But here is the thing: the build pipeline does not know it is running React. It calls a builder module. The builder returns HTML. If you swap that module for one that uses Jinja2, Handlebars, Svelte, or raw string concatenation — the pipeline does not notice. The API does not change. The taxonomy does not change. The deployment does not change.

The Swap

The React build pipeline compiles two JSX components — Post.jsx and Index.jsx — through esbuild, then calls renderToString. It lives in a single Python file that shells out to a Node process.

The Jinja2 build pipeline loads two HTML templates — post.html and index.html — and calls template.render(). It lives in a single Python file that imports Jinja2.

Swapping between them is one environment variable: ACTIVE_RENDERER. Or if you prefer, just replace the builder module file. The rest of the system is oblivious.

There is no migration. There is no compatibility layer. There is no adapter pattern. The builder interface is so thin that swapping implementations is less work than configuring most frameworks.

Why This Matters

WordPress themes took years to develop because the theme system was entangled with the runtime. You had to learn template tags, the loop, hook priorities, conditional tags, template hierarchy, child themes. The theme was not a function that returns HTML — it was a participant in a complex execution model.

Hugo themes are simpler but still require learning Go template syntax, partial templates, shortcodes, and Hugo’s specific taxonomy system. The theme is coupled to the static site generator’s mental model.

note2cms themes are coupled to nothing. A React developer writes JSX. A Python developer writes Jinja2. A Svelte developer writes Svelte. They all produce the same thing: an HTML string that the pipeline deploys.

The Junior Developer Test

Ask a junior React developer to build a note2cms theme. They receive props, they return JSX. They already know how to do this. It is literally their daily task at work.

Ask a junior Python developer to build a note2cms theme. They write a Jinja2 template with variables. They learned this in week two of their bootcamp.

Neither of them needs to learn note2cms. They already know how to produce HTML from data. That is the entire skill required.

Compare this to asking a junior developer to build a WordPress theme, a Hugo theme, or a Gatsby theme. The ramp-up time is measured in weeks, not minutes. The documentation is measured in chapters, not paragraphs.

No Batteries

Batteries-included means the framework chose for you. It chose your templating language, your data layer, your build tool, your conventions. When that choice was made in 2005, it was the right call — the ecosystem had not yet produced best-in-class standalone tools for each concern.

The ecosystem matured. The batteries are now available separately, and the standalone versions are better than the bundled ones. React is a better component model than any framework’s built-in templates. Jinja2 is a better template engine than most bundled alternatives. The era of batteries-included is over for the same reason the era of integrated stereo systems is over — the components got too good to bundle.

note2cms has no batteries. It has hot-swappable parts. The templating slot accepts anything. The database slot accepts SQLite or PostgreSQL or whatever you wire in. The deployment slot accepts local filesystem or GitHub Pages or S3 or anything that can receive an HTML file.

Every part is replaceable. No part is required. The only thing that persists is the Markdown — your words, the only thing that was ever supposed to persist.


A theme is a function. Data in, markup out. Everything else is ceremony.