Skip to content

[AnalogJS v3] Futuristic Performance: MD / MDX / MDC Rendering in Analog #2158

Description

@benpsnyder

A deep-dive into how Analog can achieve world-class markdown rendering performance by adopting native parsers, streaming architectures, and modern component-driven authoring formats.

Status Update (Mar 25, 2026)

This proposal has materially advanced on alpha over the last two weeks.

  • Merged into alpha: PR #2156 (experimental md4x renderer pipeline) and PR #2157 (content DevTools panel) are no longer speculative; they landed on alpha on Mar 23 and are included in the v3.0.0-alpha.15 line.
  • Supporting foundations also merged: PR #2148 (schema validation), PR #2149 (typed server primitives), PR #2150 (Shiki/platform cleanup), PR #2144 (Vite+OXC library pipeline), PR #2141 (Nitro dev rebuild watcher), PR #2138 (artifact leak fix), PR #2125 (typed file routes + JSON-LD + Standard Schema), and PR #2176 (typed route path utilities and validation error handling).
  • Alpha has broadened beyond content alone: the branch also absorbed Nitro v3/h3 v2, Vite 8/Rolldown alignment, Tailwind v4+, Vite+Rolldown-based Angular library builds, cross-platform build fixes, and Dagger-backed CI. That matters because md4x is now landing into a platform that already modernized its build, routing, and validation story.
  • The remaining gap is narrower now: the meaningful unfinished work is not “can md4x fit into Analog?” but “how do we wire the last facade/default behaviors and validate parity confidently enough to flip the default?”

Table of Contents

  1. Current State: Analog's Content Pipeline
  2. The Performance Gap
  3. md4x: The Native C Parser That Changes Everything
  4. astro-md4x: Proof That Drop-In Native Parsing Works
  5. MDC (Markdown Components): The Authoring Future
  6. Streaming & LLM-Ready Rendering
  7. Architecture Proposal: analog-md4x
  8. Other Tools Worth Watching
  9. Benchmark Targets
  10. Migration Strategy
  11. Addendum: Additional Features and DX Opportunities

1. Current State: Analog's Content Pipeline

Analog's content system follows a marked-based, build-time discovery, lazy runtime loading architecture:

tinyglobby (discover .md files)
  → front-matter (parse YAML frontmatter)
    → marked + extensions (parse markdown → HTML)
      → Shiki or PrismJS (syntax highlighting)
        → Angular DI tokens (inject into components)
          → MarkdownComponent (render)

Current Stack

Layer Library Role
Parser marked@15 Markdown → HTML
Frontmatter front-matter@4 YAML extraction
Heading IDs marked-gfm-heading-id GitHub-style slugs
Highlighting marked-highlight + Shiki/Prism Code blocks
Discovery tinyglobby File globbing
Validation @standard-schema/spec Frontmatter schemas (merged in PR #2148)

Related Merged PRs on alpha

Several recently merged PRs now provide infrastructure that is useful (though not strictly required) for the md4x integration:

PR Title Relevance to md4x
#2148 feat(content): schema validation Adds parseRawContentFileAsync and StandardSchema validation — md4x frontmatter output can feed into this validation pipeline
#2149 feat(router): server actions defineServerRoute provides a typed handler for streaming content endpoints
#2150 feat(platform): deps-plugin + shiki fix Fixes ShikiHighlighter constructor init order, relevant if md4x reuses the Shiki highlighting abstraction
#2144 build: Vite+OXC lib pipeline Unified Vite build across all packages — simplifies adding new dependencies
#2141 fix(vite-plugin-nitro): dev-server watcher Adds file watchers on src/server/ — a similar pattern could be extended to content directories
#2146 chore: modernize templates Updates generated project tsconfigs to moduleResolution: "bundler", which is the recommended setting for Vite-based projects
#2138 fix: artifact leak Build correctness fix — good to have stable before adding new renderers
#2125 feat(router): typed file routes + JSON-LD Typed route generation, Standard Schema validation, and JSON-LD support give md4x a much stronger destination runtime
#2176 chore: typed route path utilities polish Follow-up validation/path handling work tightens the typed routing surface that content features can plug into

Key insight: The ContentRenderer abstract class (packages/content/src/lib/content-renderer.ts) is already a clean interface with render(content) returning { content, toc }. A new Md4xContentRendererService can be added without modifying any existing code. The file loading pipeline (CONTENT_FILE_LOADER, contentFileResource) is fully renderer-agnostic. None of the PRs above are blockers for the md4x work — they are helpful but independent.

Already Implemented on alpha

The following have been built, merged, and are already part of the current alpha branch:

  • PR feat(content): add experimental md4x content rendering pipeline #2156feat(content): add experimental md4x content rendering pipeline
    • withMd4xRenderer() — NAPI renderer (50-70x faster, server/build-time)
    • withMd4xWasmRenderer() — WASM renderer (~100KB, 3-6x faster, browser/CSR)
    • withMdcComponents() + MdcRendererDirective — MDC component bridge for Angular
    • streamMarkdown() — streaming markdown-to-HTML with heal() for AI content
    • 94 new tests across all features, all @experimental
  • PR feat(content): add experimental content DevTools browser panel #2157feat(content): add experimental content DevTools browser panel
    • contentDevToolsPlugin() — Vite plugin, dev-only
    • DevToolsContentRenderer + withContentDevTools() — instrumented renderer wrapper
    • Floating panel showing parse time, renderer name, TOC, content stats
    • 10 new tests, all @experimental

What's Remaining

  • No merged analog() facade integration yetcontent.renderer: 'md4x' is still not wired into the platform plugin
  • No MDX support — can't embed Angular components via JSX syntax (MDC is the alternative path)
  • No AST access exposed to consumers — md4x's parseAST() is used internally but not yet surfaced as a public API
  • No cross-build persistence caching — transform cache exists but no disk persistence between builds
  • No dual-render CI parity validation — marked vs md4x output comparison script not yet built

2. The Performance Gap

The difference between JavaScript-based markdown parsers and native implementations is staggering:

JS markdown pipeline vs md4x (C/NAPI)

Operation JS pipeline md4x (NAPI) Speedup
Simple render ~8.2K ops/s ~443K ops/s 54x
Complex document ~670 ops/s ~49K ops/s 73x
Frontmatter parse ~4.5K ops/s ~155K ops/s 34x
Processor init ~1.8M ops/s ~11.8M ops/s 6.5x

(Benchmarks from astro-md4x comparing @astrojs/markdown-remark (remark/unified-based JS pipeline) vs md4x. Direct marked-vs-md4x benchmarks would differ but the order of magnitude is representative.)

md4x vs markdown-it

Operation markdown-it md4x (NAPI) md4x (WASM)
Medium render 21.41 µs 3.32 µs 5.76 µs
Speedup baseline 6.4x 3.7x

For a content-heavy Analog site with 500+ markdown files, this translates from multi-second builds to sub-second builds for the markdown phase alone.


3. md4x: The Native C Parser That Changes Everything

unjs/md4x is the most compelling advancement in the markdown tooling space. Built on mity/md4c (a C99 CommonMark parser), compiled with Zig, and distributed as both NAPI and WASM binaries.

Why md4x Matters for Analog

1. Dual Runtime Strategy

NAPI (Node.js)     → SSR, build-time, prerendering
WASM (~100KB gzip) → Browser, Edge, Deno, Bun, Workers

This maps perfectly to Analog's hybrid SSR/CSR architecture.

2. Multiple Output Formats

import { renderToHtml, parseAST, parseMeta } from 'md4x/napi';

const html = renderToHtml(markdown); // HTML string
const ast = parseAST(markdown);      // ComarkTree (traversable)
const meta = parseMeta(markdown);    // { frontmatter, headings }

Each call is a separate native parse (not a single shared parse), but they are all fast enough that calling both renderToHtml + parseMeta is still dramatically cheaper than a single marked.parse().

3. Efficient Parsing Architecture
The underlying md4c parser uses a callback-driven approach rather than building a full intermediate AST in C. md4x wraps this with JS-level allocations for the output, so memory usage is not truly constant, but it is significantly lower than pure-JS parsers for large documents.

4. Built-In MDC (Comark) Support

::alert{type="warning"}
This is a warning with **bold** text.

#title
Custom Title
::

Native component syntax support — no plugins, no overhead.

5. Built-In Frontmatter (via libyaml)
No separate front-matter or gray-matter dependency. YAML parsing happens at the C level.

6. Markdown Healing for LLM Streams

import { heal } from 'md4x/napi';

// Fixes incomplete markdown from streaming AI output
heal('**bold text without closing');
// → '**bold text without closing**'

// Or render with healing in one pass
renderToHtml(partial, { heal: true });

7. GFM + Extensions Out of the Box
Tables, task lists, strikethrough, alerts, LaTeX math, wiki links, underline, inline attributes — all native, zero plugin overhead.


4. astro-md4x: Proof That Drop-In Native Parsing Works

pi0/astro-md4x demonstrates that replacing a framework's entire markdown pipeline with md4x is viable and produces dramatic results:

  • Drop-in replacement: "@astrojs/markdown-remark": "npm:astromd4x@latest"
  • API compatible: Same createMarkdownProcessor() interface
  • 50-70x faster rendering with a single native dependency
  • GitHub-slugger compatible heading IDs

Lessons for Analog

  1. Compatibility layer works — md4x can sit behind an existing API surface
  2. Incremental adoption — start with build-time rendering, expand to runtime
  3. Feature gaps are manageable — syntax highlighting and remark/rehype plugins are the main gaps, both solvable

5. MDC (Markdown Components): The Authoring Future

MDC (Markdown Components) — implemented in md4x under the name Comark — is a compelling authoring advancement over MDX. While MDX requires JSX knowledge and a full compiler pipeline, MDC extends standard markdown syntax with component capabilities.

MDC vs MDX vs Plain Markdown

Feature Markdown MDX MDC
Learning curve Low High (JSX) Low (markdown-native)
Component support None Full JSX Block + inline components
Props N/A JSX attributes {key="value"} syntax
Slots N/A Children Named #slot syntax
Parser complexity Low Very high (needs SWC/Babel) Moderate
Build perf Fast Slow (JS transpilation) Fast (native in md4x)
Framework coupling None React-heavy Framework-agnostic

MDC Syntax Examples

Block Components:

::card{icon="rocket" title="Performance"}
md4x renders markdown **73x faster** than traditional JS parsers.

#footer
Built with native C and distributed as WASM.
::

Inline Components:

Press the :button[Submit]{color="primary"} button to continue.

Nested Components:

::grid{cols=2}
:::card
Left column content
:::

:::card
Right column content
:::
::

Why MDC is Perfect for Angular/Analog

  • No JSX transpilation — keeps the build pipeline simple
  • Template-friendly — MDC's attribute syntax maps cleanly to Angular inputs
  • Progressive — valid markdown with or without component rendering
  • Native in md4x — zero plugin overhead for parsing

6. Streaming & LLM-Ready Rendering

The heal() function in md4x opens up a class of features that no JS parser currently offers:

Real-Time AI Content Rendering

// In an Analog API route or server action
import { renderToHtml, heal } from 'md4x/napi';

export default defineEventHandler(async (event) => {
  const stream = await callLLM(prompt);

  return new ReadableStream({
    async start(controller) {
      let buffer = '';
      for await (const chunk of stream) {
        buffer += chunk;
        const html = renderToHtml(buffer, { heal: true });
        controller.enqueue(html);
      }
      controller.close();
    },
  });
});

Healing Performance

Input Size md4x (NAPI) remend (JS) Speedup
Small 703 ns 3.71 µs 5.3x
Medium 4.14 µs 47.8 µs 11.5x
Large 95.1 µs 10.95 ms 115x

7. Architecture Proposal: analog-md4x

Phase 1: Build-Time Native Parsing

Replace marked with md4x/napi for all build-time markdown processing.

tinyglobby (discover files)
  → md4x/napi parseMeta() (frontmatter + headings in one pass)
    → md4x/napi renderToHtml() (markdown → HTML)
      → Shiki (syntax highlighting via custom highlighter hook)
        → Angular DI tokens

Impact: 50-70x faster content builds. Zero API changes for users.

Phase 2: Runtime WASM Rendering

For CSR/lazy-loaded content, ship md4x/wasm (~100KB gzip) instead of marked (~40KB).

// Already implemented in PR #2156 as Md4xWasmContentRendererService
provideContent(withMd4xWasmRenderer());

Impact: 3-6x faster client-side rendering. The ~100KB WASM binary replaces marked + all its plugins.

Phase 3: MDC Component Bridge

Map MDC AST nodes to Angular components at render time.

// Already implemented in PR #2156 — uses lazy-loaded component factories
provideContent(
  withMd4xRenderer(),
  withMdcComponents({
    alert: () => import('./alert.component').then(m => m.AlertComponent),
    card: () => import('./card.component').then(m => m.CardComponent),
  }),
);

The md4x AST output for ::alert{type="warning"} produces structured JSON nodes (["alert", { "type": "warning" }, ...]) that the MdcRendererDirective walks and maps to Angular component instantiation — no JSX compiler needed.

Phase 4: Streaming Content

Leverage heal() for:

  • AI-generated content pages — render as tokens arrive
  • Collaborative editing — live preview of incomplete markdown
  • Progressive SSR — stream HTML chunks as markdown sections are parsed

8. Other Tools Worth Watching

Native/High-Performance Parsers

Tool Language Notes
md4x C + Zig Best-in-class. NAPI + WASM. MDC native.
mdxjs-rs Rust MDX → JS compiler in Rust. Used by Next.js. No plugin support yet.
markdown-rs Rust CommonMark + GFM in Rust. Powers mdxjs-rs.
comrak Rust GitHub's markdown renderer. CommonMark + GFM. Battle-tested at scale.
pulldown-cmark Rust Pull-based streaming parser. Very fast, very low memory.

Syntax Highlighting

Tool Notes
Shiki TextMate grammar-based. Beautiful output. Already supported in Analog.
Starry Night Like GitHub's highlighting. 600+ grammars.
Twoslash TypeScript type annotations inline in code blocks. Incredible for docs.
Expressive Code Code blocks with frames, diffs, line markers, word wrap.
rehype-pretty-code Shiki-powered with line highlighting, word highlighting, and more.

Content Authoring Frameworks

Tool Notes
Contentlayer Type-safe content SDK. Validates content at build time. (Dormant but influential.)
Velite Spiritual successor to Contentlayer. Zod schemas, build-time validation.
Content Collections Framework-agnostic type-safe content. Works with any bundler.
Fumadocs Full documentation framework. MDX + search + OpenAPI.

Component-in-Markdown Solutions

Tool Notes
MDC / Comark Native in md4x. Framework-agnostic component syntax.
MDX v3 JSX in markdown. Mature ecosystem. Heavy build cost.
mdit-vue markdown-it + Vue SFCs. Powers VitePress.
Markdoc Stripe's system. Tag-based components. Great validation.

Markdown-it Plugin Ecosystem

Collection Plugins Notes
mdit-plugins 33+ Alerts, containers, KaTeX, tabs, task lists, and more. 100% test coverage.

9. Benchmark Targets

What "amazing" looks like for Analog's content pipeline:

Build-Time Goals (estimated, not benchmarked against Analog specifically)

Metric Estimated (marked) Target (md4x) Expected improvement
100 files parse + render ~150ms ~3ms ~50x
500 files parse + render ~750ms ~15ms ~50x
Frontmatter extraction (500 files) ~110ms ~3ms ~35x
TOC generation (500 files) separate pass included in parse 2x (eliminated)
Cold processor init ~550ns ~85ns ~6.5x

These estimates are extrapolated from the astro-md4x benchmarks and md4x's published per-document numbers. Actual Analog performance should be measured after integration.

Runtime Goals (CSR, estimated)

Metric Estimated (marked) Target (md4x/wasm) Expected improvement
Single page render ~120µs ~20µs ~6x
Bundle size (parser) ~40KB + plugins ~100KB (all-in-one) parity*
First render (cold) parse + highlight cached HTML via TransferState 0ms (hydration)

*md4x WASM is larger but replaces marked + front-matter + marked-gfm-heading-id + marked-highlight, netting roughly equivalent total size with more capability.

DX Goals

Feature Status Target
Component-in-markdown Implemented (PR #2156, @experimental) MDC syntax with Angular component mapping
Frontmatter validation Implemented (PR #2148) Standard Schema + build-time errors
Live preview (dev) Full page reload HMR with incremental re-parse
AI content streaming Implemented (PR #2156, @experimental) heal() + streaming SSE
AST access Used internally, not public API parseAST() for custom transforms
Content DevTools Implemented (PR #2157, @experimental) Parse time, TOC, renderer info

10. Migration Strategy

Recently Merged Supporting Work

These PRs provide complementary infrastructure. The md4x renderer (PR #2156) was built independently, but alpha now already includes the surrounding pieces needed for the next stage:

Step 1: Add md4x as an Alternative Renderer (Non-Breaking) — DONE (PR #2156)

The ContentRenderer abstract class was already a clean interface. Md4xContentRendererService and Md4xWasmContentRendererService implement it without modifying any existing code.

provideContent(withMd4xRenderer());                    // NAPI (server)
provideContent(withMd4xWasmRenderer());                // WASM (browser)
provideContent(withMd4xRenderer({ heal: true }));      // with streaming heal
provideContent(withMd4xRenderer({ highlighter: fn })); // with Shiki hook
  • md4x/napi for build-time, md4x/wasm for runtime — both implemented
  • marked remains default — md4x is opt-in via provider swap
  • 94 tests covering rendering, TOC, frontmatter, GFM, code blocks, highlighting, healing, provider wiring

Step 2: MDC Component Registration — DONE (PR #2156)

withMdcComponents() and MdcRendererDirective are implemented. The directive walks md4x's ComarkTree, matches component names to the registry, and instantiates Angular components via ViewContainerRef.createComponent().

provideContent(
  withMd4xRenderer(),
  withMdcComponents({
    alert: () => import('./components/alert.component').then(m => m.AlertComponent),
    card: () => import('./components/card.component').then(m => m.CardComponent),
  }),
);

11 tests covering HTML elements, registered components, unregistered fallback, null/empty AST, attributes, and no-registry scenarios.

Step 3: Streaming Content API — DONE (PR #2156)

streamMarkdown() is implemented. It works standalone with any ReadableStream<string> input:

// In a Nitro API route (h3)
import { streamMarkdown } from '@analogjs/content';

export default defineEventHandler(async (event) => {
  const llmStream = getAIStream(event);
  return streamMarkdown(llmStream, { heal: true });
});

With PR #2149 merged, streamMarkdown can also be used inside defineServerRoute handlers for typed server-side streaming.

Step 4: Default to md4x

After validation across the ecosystem, make md4x the default renderer and deprecate the marked path. The main prerequisites that still need explicit follow-through are platform facade wiring and dual-render parity validation.


Summary

The path to "amazing" markdown rendering in Analog is clearer now than when this issue was opened. Phase 1-3 are built and merged, and several adjacent router/platform pieces also landed on alpha:

Phase Status PR
md4x NAPI renderer (50-70x faster) Done #2156
md4x WASM renderer (3-6x faster in browser) Done #2156
MDC component bridge for Angular Done #2156
Streaming markdown with heal() Done #2156
Content DevTools panel Done #2157
Schema validation foundation Done #2148
Typed server primitives / route integration Done #2149
Typed file routes + JSON-LD destination Done #2125
analog() facade integration Not started
Dual-render CI parity validation Not started
Default to md4x Blocked on parity validation

What's next:

  1. analog() facade integration — wire content.renderer: 'md4x' into the platform plugin so users don't need to touch provideContent() directly
  2. Dual-render CI validation — build tools/scripts/validate-md4x-parity.mts to compare marked vs md4x output across all workspace markdown files
  3. Exploit the newly merged routing stack — connect md4x content flows more directly with the typed file-route / JSON-LD / server-route work now on alpha
  4. Content Collections — schema-per-directory with build-time validation (StandardSchema + md4x)
  5. Default to md4x — after CI parity validation passes, switch the default renderer

The combination of md4x's native performance, MDC's elegant component syntax, and Analog's Angular-powered rendering still creates an opportunity to build the fastest, most developer-friendly content pipeline in the meta-framework space. The important difference now is that much of the enabling work is already merged on alpha, so the roadmap is no longer hypothetical; it is mostly about finishing integration, parity validation, and a safe default switch.


11. Addendum: Additional Features and DX Opportunities

Added after analyzing the merged alpha work and the recent pull requests that landed around it.

Content Collections with Per-Collection Schemas

StandardSchema validation (PR #2148) + md4x = validated, typed, blazing-fast content collections. Define a schema per collection directory and fail the build when any file has invalid frontmatter — the Analog equivalent of Astro's content collections.

// content/blog/_schema.ts
import * as v from 'valibot';

export const BlogSchema = v.object({
  title: v.string(),
  date: v.pipe(v.string(), v.isoDate()),
  tags: v.optional(v.array(v.string()), []),
  draft: v.optional(v.boolean(), false),
});

Now that PR #2148 is merged, md4x's native libyaml frontmatter output can feed into the parseRawContentFileAsync(raw, schema) pipeline. The schema validates at C-speed parsing + JS-speed validation, which is the optimal split.

Incremental Content Builds

md4x's SAX-style parsing + the dev-server file watcher from PR #2141 = only re-parse changed files. The Nitro rebuild watcher already watches src/server/ for changes; extending it to src/content/ would trigger md4x re-renders for only the modified files instead of the full content directory.

Combined with Nx's task caching (already configured for content builds), this could achieve sub-100ms incremental content rebuilds for sites with thousands of markdown files.

Dual-Render CI Validation

Before switching the default renderer from marked to md4x, run both in CI and diff the HTML output:

// tools/scripts/validate-md4x-parity.mts
for (const file of contentFiles) {
  const markedHtml = markedRenderer.render(file);
  const md4xHtml = md4xRenderer.render(file);
  const diff = htmlDiff(normalize(markedHtml), normalize(md4xHtml));
  if (diff.length > 0) {
    report.push({ file, diff });
  }
}

This provides confidence to maintainers that the switch is safe, and catches edge cases in GFM extensions, heading ID generation, or HTML entity handling before users hit them.

MDC-to-Angular Component Bridge with Signal-Based Props — Partially DONE (PR #2156)

The basic bridge is implemented: MdcRendererDirective walks the md4x ComarkTree, matches component names to the MDC_COMPONENTS registry, and instantiates Angular components via ViewContainerRef.createComponent() with MDC attributes bound as inputs via setInput().

::product-card{id="42" featured=true}
Check out this product!
::

Maps to:

@Component({ selector: 'product-card', ... })
class ProductCardComponent {
  id = input.required<string>();
  featured = input<boolean>(false);
}

Future enhancement: The current implementation binds attributes as strings via setInput(). A future iteration could use Angular's input() signal metadata to coerce types (e.g., featured=trueboolean) and validate required inputs at render time.

Content DevTools (Browser Panel) — DONE (PR #2157)

Implemented as contentDevToolsPlugin() (Vite plugin, apply: 'serve' only) + DevToolsContentRenderer (instrumented wrapper) + withContentDevTools() provider. Currently shows:

  • Active renderer name and parse time with color-coded speed indicators
  • Content stats — character count, heading count
  • TOC preview — clickable heading tree with anchor links

Future enhancements for the DevTools panel (not yet built):

  • AST explorer — interactive ComarkTree view for the current page's content
  • Frontmatter inspector — raw attributes, schema validation result, derived fields
  • Render diff — side-by-side marked vs md4x output for migration validation
  • Performance flame chart — breakdown of parse vs highlight vs TOC extraction time

AI Content Authoring with Live Preview

Combine md4x's heal() function with the server actions from PR #2149 and the SSE infrastructure demonstrated in the analog-app demo:

// Server action: stream AI-generated content
export default defineServerRoute({
  POST: async ({ event }) => {
    const { prompt } = await parseRequestData(event);
    const stream = await generateContent(prompt);

    return new ReadableStream({
      async start(controller) {
        let buffer = '';
        for await (const chunk of stream) {
          buffer += chunk;
          // heal() fixes incomplete markdown in-flight
          const html = renderToHtml(buffer, { heal: true });
          controller.enqueue(
            `data: ${JSON.stringify({ html, raw: buffer })}\n\n`,
          );
        }
        controller.close();
      },
    });
  },
});

On the client, an Angular component consumes the SSE stream and renders the healed HTML in real-time. This enables:

  • AI blog post drafting with instant preview
  • Documentation generation from code comments
  • Content translation with streaming side-by-side comparison

The heal() performance (703ns for small inputs, 4.14µs for medium) is fast enough for 60fps rendering of streaming content.

Edge-Rendered Content with md4x/WASM

md4x's WASM build (~100KB gzip) runs in Cloudflare Workers, Deno Deploy, and Vercel Edge Functions — environments without Node.js native module support. This enables:

  • Content-at-the-edge — render markdown to HTML at the CDN edge, eliminating origin round-trips for content pages
  • Dynamic content personalization — render different markdown variants per user segment at the edge
  • Preview deployments — render draft content in edge functions without a full Node.js server

Combined with Analog's existing Nitro presets for Cloudflare and Vercel, this is a natural extension of the deployment story.

Markdown Language Server Protocol (LSP) Integration

md4x's AST output enables building a markdown LSP that understands Analog's content conventions:

  • Frontmatter autocomplete — driven by the StandardSchema definitions from content collections
  • MDC component completion — suggest registered components from withMdcComponents()
  • Route link validation — verify [link](/blog/post) targets exist in the file-based routes
  • Live error squiggles — frontmatter validation errors appear inline in the editor

This would work as a VS Code extension or any LSP-compatible editor, using the same schema definitions that validate at build time.

Content Caching with ETag-Based Invalidation

md4x's deterministic rendering (same input = same output, always) enables aggressive HTTP caching. Since md4x doesn't expose a hash function, a standard Node.js hash (e.g., crypto.createHash('sha256')) on the raw markdown provides an ETag:

import { createHash } from 'node:crypto';
import { renderToHtml } from 'md4x/napi';

// Nitro middleware
export default defineEventHandler(async (event) => {
  const etag = `"${createHash('sha256').update(rawMarkdown).digest('hex').slice(0, 16)}"`;

  if (event.headers.get('if-none-match') === etag) {
    return new Response(null, { status: 304 });
  }

  const html = renderToHtml(rawMarkdown);
  return new Response(html, {
    headers: { ETag: etag, 'Cache-Control': 'public, max-age=3600' },
  });
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions