3.4 KiB
Hugo vs Thor: Why Thor Doesn't Need Shortcode Context Isolation
Hugo's Shortcode Isolation
Hugo strictly separates shortcode context from layout template context. Shortcodes run during markdown rendering, before layouts. They get a limited context (.Page, .Site, .Params), not the full layout rendering state.
Why Hugo does this
-
Circular dependencies — Hugo shortcodes output into
.Content. Layouts read.Contentand compute derived properties (.TableOfContents,.WordCount,.ReadingTime). If shortcodes could access these computed properties, you'd get circular dependencies (shortcode output → computed property → shortcode reads it). -
Multiple layouts/themes — Hugo supports user-selectable themes and multiple layouts per section type. A shortcode must produce identical output regardless of which layout renders it. Isolating context guarantees portability.
-
Shared mutable page object — Hugo builds a rich
Pageobject that both shortcodes and layouts access. Context isolation prevents shortcodes from mutating layout-visible state. -
Security — Shortcodes live in content files (potentially user-authored). Full template access would let content authors inject arbitrary logic, access sensitive config, or break site structure.
-
Caching — Hugo caches shortcode output independently. Predictable context = predictable cache behavior.
Why this doesn't apply to thor
-
No computed page properties — The content Mustache pass runs on raw markdown (before cmark). The layout Mustache pass runs on finished HTML (after cmark). There's no shared
.Contentobject or derived properties. The two passes are at different pipeline stages with no shared mutable state. -
Single layout set — Thor has one set of templates (
base.html+ page templates). No theme switching, no multiple layout variants per section. Portability across layouts isn't a concern. -
Explicit data passing — Each
mustache.rendercall receives its own data struct. Content pass gets frontmatter + params. Layout pass gets page data + site config. No shared mutable object between them. -
Single author — Content is authored by the site owner. No untrusted user-generated content.
-
No caching — Thor rebuilds from scratch every time. No cache invalidation concerns.
Thor's approach
Thor runs Mustache on content before cmark as a pre-processing step, then runs Mustache on layout templates after cmark as a post-processing step. These are two independent mustache.render calls:
content (markdown)
→ mustache.render(content, content_data, partials) ← content pass
→ cmark markdown_to_html
→ inject_sidenotes / inject_alerts / highlight_code
→ mustache.render_in_layout(template, page_data, layout, partials) ← layout pass
→ final HTML
The content pass can use Mustache variables, partials ({{> ./file}}), sections, and conditionals freely. The layout pass wraps the rendered content in the page chrome. They share nothing except the data we explicitly choose to pass.
When this WOULD matter for thor
If thor ever adds:
- Computed page properties (table of contents, reading time, word count)
- Multiple selectable themes/layouts
- User-generated content / multi-author support
- Partial template caching
...then context isolation between the content and layout passes would become relevant. Until then, it's unnecessary complexity.