Marker pairs over template engines
My website's _fragments/nav.html is shared between fifteen pages. There is no template engine involved. There is a 200-line Rust binary that does literal text substitution between HTML comments.
This was a deliberate choice over Astro components, Eleventy includes, Hugo partials, Jinja, Liquid, Handlebars, EJS, and the dozen other templating systems I've used over the years. They all work. They are all overkill for the problem, and crucially, they are all noise that AI agents have to model in addition to the actual job. Working with agents, you start picking primitives the agents can reason about without first learning a DSL. Marker pairs are the smallest such primitive I've found for shared HTML.
This article is about what the smaller primitive looks like and why I prefer it for static sites with a small number of shared blocks.
Why template engines feel like overkill for static sites
A template engine adds three things to your stack: a syntax for inclusion ({% include %}, {{> partial}}, <Component />), a build step that produces HTML from your template-decorated source, and a development server that re-runs that build on file changes.
Each of those three things has an associated cost. The syntax is something you have to learn and that some editors don't highlight correctly. The build step means the file you author is not the file the browser sees, so you debug at two levels. The dev server means you have to start something before you can see your changes; "open the file" stops being the simplest workflow.
For a site with five-to-fifty pages and a handful of shared blocks (head, nav, footer, sometimes a CTA), those costs are real and the value you get from them is small. You're using a templating system because you don't want to copy-paste the nav into every file, and copy-paste is the actual problem you're solving. Everything else the template engine offers, conditional rendering, loops, layouts, slots, is not what you're reaching for in the moment.
If the problem is "I have shared HTML and I don't want to copy-paste it," there is a smaller answer.
The marker-pair primitive
A marker pair is a comment-tagged region in a target file:
<!-- fragment:nav -->
<nav>... whatever the current nav HTML is ...</nav>
<!-- /fragment:nav -->
A source file at _fragments/nav.html holds the canonical nav. Running fragments sync walks every HTML file in the project, finds each marker pair, and replaces the content between with the source file's content. The markers themselves stay. The file remains valid HTML at all times.
That's the whole primitive. There's no DSL. There's no expression syntax. There are no slots, no layouts, no inheritance. There's a source file and a target region, and the source is copied into the target on a sync.
The properties this gives you are worth listing because they're often invisible:
The target file is always valid HTML. Open it in a browser, view source, hand it to someone, it's a file.
There's no separate build artifact. The file in your editor is the file that ships.
A diff against the previous version is a diff of HTML, not of template source. You can review it in any code review tool without needing the engine to render.
If you stop using the tool tomorrow, the marker comments are unobtrusive HTML comments. Nothing breaks; you just lose the sync convenience.
An agent can read the file directly and understand what's there. No mental compile step.
Walking through the implementation
The Rust binary is small enough to describe end-to-end.
fragments sync reads fragments.toml (optional config, markers default to fragment:, source dir defaults to _fragments/, target dir defaults to .). It loads every .html file in _fragments/ into a name → contents map. It walks the target dir for HTML files, opens each one, and for every fragment file, finds the marker pair and replaces the content between.
The replacement is literal string substitution. There's no parser. There's no AST. There's no escaping logic. The fragment content is HTML, the target content is HTML, the splice is a byte-level replacement.
There's a fragments check subcommand that does the same walk but exits non-zero if any target's content doesn't match what sync would produce. This is the CI gate.
There's a fragments watch subcommand that uses notify to re-sync when source files change.
That's the whole tool. The marker-pair primitive is so simple that the implementation almost writes itself, which is the point.
What this enables (and doesn't)
It enables: edit _fragments/nav.html, run fragments sync, see the change reflected on every page that includes the nav, ship the result.
It doesn't enable: passing parameters to a fragment ("show this nav with the 'Pricing' link active"), conditional inclusion ("only on logged-in pages"), templating expressions, internationalization, anything dynamic.
The first one (parameterized fragments) is the most common thing I miss. My workaround so far is to have multiple fragment files: nav.html for the homepage, nav-project.html for project pages with depth-aware paths, nav-blog.html for the blog. That's worked for fifteen pages. It would not work for fifty.
The decision criterion: if your shared blocks are uniform across pages, marker-pair fragments are a good fit. If your shared blocks need to vary based on page context, you've outgrown the primitive and a real templating layer is the right next step.
Why this fits agentic coding
A real templating engine is a small DSL the agent has to learn alongside its actual job. To edit a Hugo site, an agent needs to know Hugo's {{ partial }} and {{ range }} and {{ with }} and the variable scoping rules and the subtleties of _index.md versus index.md. To edit an Astro site, the agent needs to know component imports, frontmatter syntax, slot patterns, and the rules around when a component is server-rendered versus hydrated.
A marker-pair fragment system has nothing to learn. The agent reads HTML, finds a comment that matches a pattern, and replaces the content between. There's no compile step it has to model. There's no place the syntax can fail in a way that's hard to debug.
When I dispatch an agent to edit shared content (update the nav, change a CTA text in the footer), the agent's prompt is "edit _fragments/<name>.html and run fragments sync." That's the entire mental model. There is no second step. There is no transformation pipeline.
I keep coming back to this principle: the smaller the gap between "what the agent edits" and "what the browser renders," the cheaper every iteration is. Marker-pair fragments fit that principle better than any templating engine I've used. The agentic angle was an emergent property of choosing the smaller primitive, but it's now the reason I won't go back.
When you'd want something more
I want to be specific about where this stops being a good idea.
Past about fifty pages, even uniform fragments become hard to maintain because you have many target files to keep in sync, and a typo in one of them silently breaks. You want a single template that pages declare they extend.
If you need any conditional rendering, you want a real templating language.
If you need to generate pages from data (a blog index from a list of posts, a product catalog from a JSON file), you want a static site generator.
But for the case I'm in, a personal site with around fifteen pages, a handful of shared blocks, no dynamic content, edited weekly, the marker-pair primitive is doing exactly the right amount of work. Anything more would be friction I'm paying without benefit.
If you're maintaining a static site with a templating system and you find yourself only using include and never reaching for the rest of the engine's features, this might be the smaller primitive you actually wanted.