4. Build-Time Markdown Pipeline with Dual-Theme Shiki and Mermaid

Status

Amended — 2026-04-24

Originally accepted 2026-02-13 (initial pipeline), extended through 2026-03.

Context

The blog is content-heavy and publishes on a fixed cadence. Every post needs syntax-highlighted code, some need Mermaid diagrams, and every reader needs both to work in light and dark mode, on any device, without layout shift or flash. The alternative — parsing markdown, highlighting code, and rendering diagrams at runtime — pays that cost on every visit for content that does not change between deploys.

Decision

All markdown is compiled to HTML at build time. The frontend does no markdown parsing, no syntax highlighting, and no diagram rendering at runtime.

  • tools/build/ implements a staged pipeline: discover → parse → validate → enrich → transform → generate. Output is JSON in frontend/src/assets/generated/, which the frontend fetches and renders via [innerHTML].
  • Shiki runs in the transform stage against both a light and a dark theme, emitting <div class="shiki-light"> and <div class="shiki-dark"> siblings inside a single container. CSS toggles visibility by theme.
  • Mermaid diagrams are rendered to SVG via @mermaid-js/mermaid-cli as a subprocess at build time, also as dual-theme pairs (<div class="mermaid-light"> + <div class="mermaid-dark">).
  • Custom directives (callouts, includes), table of contents, heading anchors, search index, RSS feed, sitemap, and the prerender routes.txt are all generated in the same pipeline.

Consequences

  • The frontend ships no markdown parser, no Shiki runtime, and no Mermaid runtime. Blog bundle size reflects that.
  • Code blocks and diagrams render on first paint with no flash, no layout shift, and no client-side work.
  • Content changes require a build. Fast iteration depends on the pipeline’s caching layer; a cold build with many Mermaid diagrams is slow because each diagram spawns a CLI subprocess.
  • Theme switching is pure CSS. Both themes are always in the DOM, which doubles the bytes for highlighted code and diagrams but removes all runtime theme-swap complexity.
  • Any future feature that needs runtime markdown (user-generated content, live preview) would require reintroducing a parser on the client. This is a deliberate boundary: the current system is a build-time pipeline, not a rendering engine.
  • The pipeline is the hardest part of the system to replace. Extending it (new content type, new directive, new output) is cheap; replacing it is a rewrite.

Amendment — 2026-04-24

Mermaid Diagrams Moved to Runtime Rendering

Mermaid diagrams are no longer rendered at build time. The @mermaid-js/mermaid-cli dependency and its Puppeteer/Chromium requirement have been removed from the build pipeline.

What changed: Mermaid fenced code blocks now pass through the pipeline as standard <code class="language-mermaid"> blocks. A lazy-loaded Angular directive (MermaidDirective) detects these blocks at runtime, dynamically imports the mermaid library, and renders SVGs in the browser. Theme switching is handled by re-initializing mermaid with the appropriate theme config when prefers-color-scheme changes.

What did not change: Shiki syntax highlighting remains build-time. Custom directives, TOC generation, search index, RSS, and sitemap generation are all unaffected. The pipeline stages (discover → parse → validate → enrich → transform → generate) are unchanged.

Why: The build-time approach required Puppeteer + Chromium in CI, which complicated the Cloud Build migration (ADR 0018). Runtime rendering eliminates this dependency while providing better theme switching (single render that responds to system theme, rather than dual pre-rendered SVGs toggled by CSS). The trade-off is a ~200KB lazy-loaded chunk on pages with diagrams (most pages have none).