- Feature Name: Markdown shards
- Start Date: 2025-07-26
- Preliminary patch: github
Summary
Enable markdown to be broken into “shards” that can be individually accessed from Tera templates. Doing this allows the power of Tera templates to be applied to the main page content.
Motivation
Tera templates are great. Really powerful. You can do some intricate things with navigation, taxonomy etc. in zola using them. When it comes to dealing with the main content of the page, however, you just have {{page.content | safe}}
as one big block, meaning that you can’t use that power for most of what you write.
This is fine for blog style pages, where you just want to wrap some big, simple chunk of markdownable content in some navigation, but it is limiting for more complex pages. You end up doing ugly kludges using shortcodes and archetypes to kinda do templates within templates within templates, and all that lovely Tera power is nowhere to be seen.
Various work-arounds have been talked about in the forums and in github issues. The main ones are abusing [extra]
in the front matter, using load_data
to load markdown structured inside YAML or suchlike and using Tera’s split
filter. These are effective, but something feels fundamentally “off” and hacky about all of them.
It turns out you can go a very long way to providing the flexibility to use the main markdown content with Tera’s templates with a very minimal, fully backwards compatible, change to the codebase.
Guide-level explanation
The fundamental issue here was brought over from Hugo: the building of a page is triggered by the presence of a markdown file in the content
file hierarchy. This markdown file is, however, not really a markdown file. It’s really a toml file (or yaml in Hugo’s case) with a monolithic “blessed” markdown asset appended. In many ways it would be better if the trigger was actually the presence of a toml file, and the main content was in a file referenced from the toml, just as the template is.
We are where we are, however, and by stealing a trick from multipart email, we can get a huge amount more flexibility out of that monolithic asset at almost no cost. Here’s what the markdown for a page using shards looks like:
+++
title = "Shard demo"
shard_marker = "-*-"
+++
Everything that goes here gets rendered out as normal and is available to the
templates as page.content
If a user doesn't opt in by specifying a shard_marker, everything else gets
rendered as normal and they wouldn't notice the feature.
-*- foo
The rendered html from this markdown is available to the templates as
page.shards.foo[0]
-*- foo
The rendered html from this markdown is available to the templates as
page.shards.foo[1]
-*- bar
The rendered html from this markdown is available to the templates as
page.shards.bar[0]
Your template can then do this sort of thing:
<h1>{{page.title|safe}}</h1>
<div class="main_content">{{page.content|safe}}</div>
<div class="columns">
<div class="foo">
{% for b in page.shards.foo %}
<div class="foo_block">{{b|safe}}</div>
{% endfor %}
</div>
<div class="bar">
{% for b in page.shards.bar %}
<div class="foo_block">{{b|safe}}</div>
{% endfor %}
</div>
</div>
If you don’t add that shard_marker = "xyz"
to the front matter, you wouldn’t know the feature was there. Moreover, because you are selecting what the marker is, you can select something that you are sure is not in your actual markdown. Multipart email uses this trick: Content-Type: multipart/alternative; boundary="=-=-="
to solve the same problem.
Reference-level explanation
The code modification is very simple. In components/content/src/page.rs
we modify the render_markdown
function so that it checks for the existence of self.meta.shard_marker
.
If that doesn’t exist, it procedes as it did before, giving full backward compatibility.
If it does exist, it splits the raw content on the marker and processes the first string in exactly the same way as before to expose the page.content
variable. The remaining strings are split again on the first new line, and everything between the shard marker and the new line is used as the shard identifier. The shard content is then processed just as the main content was, and pushed to a vector keyed to that identifier in a hash map. This all amounts to about 15 lines of new code.
The rest of the modifications are very minor: make shard_marker
a legitimate entry in the front matter, give the page in the library the new HashMap<String, Vec<String>>
and expose this to the tera templates – another 5 lines of code.
Drawbacks
Adds complexity to the documentation. It is somewhat hard to explain why you might need this feature until you find that you need it.
Rationale and alternatives
Another suggestion in the github issues was to add an entire new TOML block, which would potentially give even more flexibility – new meta data could be introduced with each shard. I think that this would make for a much more complex implementation and wouldn’t gain enough to be worth it, since any shard level meta data could just be added to [extra]
.
Another alternative would be to switch to using TOML files as the trigger for page creation, with content introduced as asset files referenced in the TOML files. This would be a huge change though, and would drastically break backward compatability.
The shards could also be nestable in the same fashion as multi-part email is. This might add some more structure to the data than the simple hash map of vectors proposed, but I don’t believe the complexity would be worth the additional benefit.
A final option is to make sharded markdown something that load_data
could parse. This actually has a lot going for it in terms of not touching the main codebase, but is probably a lot less likely to be discovered.
Not doing this is also fine. There are work arounds that, although ugly, work.
Prior art
The main prior art is multipart email, which solves a similar problem: how to break a simple text file that can contain pretty much any text into meaningful sections.
Unresolved questions
Do we want to do this just for pages, or do we need to do it for sections to? You can access the shards of each page from the sections anyway, so I don’t know whether you would gain much.
Future possibilities
Can’t really think of anything for this. It’s a pretty self contained change.