← All articles
7 min read

Building SSR Markdown Renderers for Content Platforms

A practical guide to server-side rendering markdown: the pipeline stages, code and image handling, sanitization, and testing it like infrastructure.

The Problem Client-Side Markdown Rendering Creates

If you render markdown to HTML in the browser — fetch the raw .md, run it through a JavaScript markdown library, inject the result into the DOM — you've made a decision that quietly costs you every time something other than a modern browser with JavaScript enabled requests that page. Search crawlers that don't execute JavaScript (or execute it on a delay, or budget it stingily) see an empty shell. Social media unfurl bots building a link preview card see nothing to extract an image or description from. RSS readers, accessibility tools, and anything doing a simple HTTP fetch instead of a full browser render all see the same blank page.

Server-side rendering (SSR) of markdown means the HTML a client receives on first request is already the finished article — headings, paragraphs, code blocks, images — with no client-side markdown parsing step required to see content. It's more work at request time on your server, but it's work you control and can cache, versus a rendering dependency you're pushing onto every client that hits your site and hoping they all handle it the same way.

The Rendering Pipeline, Step by Step

A markdown-to-HTML pipeline for a content platform has more stages than "parse markdown, get HTML," even though that's the part people focus on:

  1. Frontmatter extraction. Most content platforms store metadata (title, publish date, author, tags) as YAML frontmatter at the top of the markdown source, separate from the body. Parse this first and validate required fields exist before touching the body — a missing publish date shouldn't surface as a rendering bug three steps later.
  2. Markdown parsing to an AST. Rather than going straight from markdown text to an HTML string, parse into an abstract syntax tree first (this is how the mature JavaScript markdown ecosystem — remark and its rehype counterpart for HTML — and Go's goldmark are structured, and it's a sound pattern to follow even outside those specific libraries). An AST gives you a structured place to run transformations — rewriting image paths, adding loading="lazy" to img nodes, syntax-highlighting code blocks — before you ever serialize to a string, which is both easier to test and easier to reason about than string-manipulating HTML with regex.
  3. Sanitization. If any part of your markdown source can come from a source you don't fully trust — user submissions, scraped content, an LLM generation step — sanitize the resulting HTML before it's stored or served. Markdown itself usually allows raw HTML passthrough, which means an unescaped