Markdown + syntax highlighting

Rendered on the server with marked + shiki · 7,814 bytes of pre-highlighted HTML

Server-rendered Markdown with syntax highlighting

This entire block was parsed on a Cloudflare Worker using marked, with code blocks highlighted by shiki. The rendered HTML is streamed to the browser — neither library ships to the client.

Why this is interesting

  • marked (~50KB) converts markdown to HTML.
  • shiki (~2MB when you include themes and grammars) colorizes code blocks using real TextMate grammars — the same engine VSCode uses.

Doing syntax highlighting client-side means every visitor downloads shiki's WASM, grammars, and themes up front. Doing it on a server component means the browser only receives pre-styled HTML — no runtime cost, no bundle bloat.

Code blocks

import { createServerFn } from "@tanstack/react-start";
import { renderServerComponent } from "@tanstack/react-start-rsc";
import { Marked } from "marked";
import markedShiki from "marked-shiki";
import { createHighlighter } from "shiki";

const highlighter = await createHighlighter({
  themes: ["github-dark"],
  langs: ["tsx", "typescript", "bash", "json"],
});

const md = new Marked().use(
  markedShiki({
    highlight: (code, lang) =>
      highlighter.codeToHtml(code, { lang: lang || "text", theme: "github-dark" }),
  }),
);

const renderMd = createServerFn().handler(async () => {
  const html = await md.parse(SAMPLE_MARKDOWN);
  return { Result: await renderServerComponent(<div dangerouslySetInnerHTML={{ __html: html }} />) };
});
# No shiki in the browser:
curl https://tss-cf-ws-app.babsonmatt.workers.dev/server/markdown \
  | grep -o 'shiki'  # (empty)
{
  "server": "parses + highlights",
  "client": "receives pre-styled HTML",
  "bundle_savings": "~2MB"
}

Try it yourself

  • Open DevTools → Network → JS — search for 'shiki' or 'marked'. You won't find them.
  • View source — the <pre> blocks are already highlighted with inline styles.
  • Reload the page — this is recomputed each request on the worker.

The highlighter is created lazily and cached for the worker's lifetime, so only the first request pays the grammar-load cost.