@tanstack/markdown
Version:
A tiny, fast, deterministic Markdown renderer for blogs and documentation.
486 lines (340 loc) • 13.5 kB
Markdown
---
name: 'production-pipelines'
description: >
Audit and ship a production Markdown pipeline with explicit trust boundaries,
external syntax highlighting, parse-ahead caching, compatibility checks,
deterministic output, and bundle budgets. Load before deploying blogs, docs,
or untrusted-content rendering.
metadata:
type: lifecycle
library: '@tanstack/markdown'
library_version: '0.0.13'
requires:
- 'render-markdown'
sources:
- 'TanStack/markdown:docs/core-concepts/security.md'
- 'TanStack/markdown:docs/core-concepts/document-model.md'
- 'TanStack/markdown:docs/core-concepts/syntax-profile.md'
- 'TanStack/markdown:docs/guides/react.md'
- 'TanStack/markdown:docs/guides/syntax-highlighting.md'
- 'TanStack/markdown:docs/guides/performance.md'
- 'TanStack/markdown:docs/guides/testing.md'
- 'TanStack/markdown:docs/comparison.md'
- 'TanStack/markdown:src/utils.ts'
- 'TanStack/markdown:tests/security.test.tsx'
- 'TanStack/markdown:tests/bundle-size.test.ts'
---
This skill builds on `render-markdown`. Read it first for the supported syntax, AST, parser options, and renderer contracts.
# TanStack Markdown — Production Pipeline Checklist
Run every section before deploying a blog, documentation site, or user-content renderer.
## Trust Boundary Checks
### Check: Classify every Markdown source
Expected:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderUntrustedMarkdown(
source: string,
sanitize: (html: string) => string,
): string {
const rendered = renderHtml(source)
return sanitize(rendered)
}
```
Fail condition: Untrusted input reaches `allowHtml`, an unaudited extension `renderHtml` hook, or an unaudited highlighter.
Fix: Separate trusted and untrusted entry points, keep trusted callbacks disabled for untrusted content, and enforce application link, image, and final-HTML policy.
## Highlighting Checks
### Check: Return trusted code contents only
Expected:
```ts
import { renderHtml } from '@tanstack/markdown/html'
import { createHighlighter } from '@tanstack/highlight/core'
import { plaintext } from '@tanstack/highlight/languages/plaintext'
import { ts } from '@tanstack/highlight/languages/ts'
import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
const highlighter = createHighlighter({ languages: [plaintext, ts] })
export const html = renderHtml('```ts {1}\nconst answer = 42\n```', {
highlighter: createTanStackMarkdownHighlighter(highlighter),
})
```
Fail condition: The callback returns a complete `<pre><code>` tree, does not escape source code, or comes from an unreviewed transform.
Fix: Return only escaped markup for the renderer-owned `<code>` contents, and run highlighting during ingestion, build, or server rendering.
## Compatibility Checks
### Check: Validate the actual content corpus
Expected:
```bash
MARKDOWN_CORPUS_DIRS=../site/src/blog:../site/docs pnpm run test:corpus
pnpm run corpus:audit:tanstack
pnpm run corpus:audit:external
```
Fail condition: Adoption relies only on CommonMark examples or a comparison table instead of the site's Markdown.
Fix: Add downstream content directories, preserve practical regressions with focused fixtures, and require renderer and bundle accounting for new syntax.
### Check: Verify deterministic output
Expected:
```ts
import { renderHtml } from '@tanstack/markdown/html'
import { parseMarkdown } from '@tanstack/markdown/parser'
const source = '# Deterministic\n\n- one\n- two'
const firstDocument = JSON.stringify(parseMarkdown(source))
const secondDocument = JSON.stringify(parseMarkdown(source))
const firstHtml = renderHtml(source)
const secondHtml = renderHtml(source)
if (firstDocument !== secondDocument || firstHtml !== secondHtml) {
throw new Error('Markdown output is nondeterministic')
}
```
Fail condition: Identical source and options produce different serialized AST or HTML.
Fix: Remove time, randomness, environment state, and unstable ordering from extensions and render callbacks.
## Performance and Cache Checks
### Check: Parse once with final options
Expected:
```ts
import type { MarkdownDocument } from '@tanstack/markdown'
import { renderHtml } from '@tanstack/markdown/html'
import { parseMarkdown } from '@tanstack/markdown/parser'
const cache = new Map<string, MarkdownDocument>()
export function renderCachedArticle(key: string, source: string): string {
const cacheKey = `markdown-0.0.13:${key}`
let document = cache.get(cacheKey)
if (!document) {
document = parseMarkdown(source, {
frontmatter: true,
headingIds: true,
})
cache.set(cacheKey, document)
}
return renderHtml(document)
}
```
Fail condition: Stable content is reparsed per request, or parser options/extensions change after the AST is cached.
Fix: Build the AST with final parse options, version persisted cache keys, invalidate stored ASTs when node contracts change, and use only narrow entry points.
### Check: Enforce bundle budgets
Expected:
```bash
pnpm run size
pnpm test -- tests/bundle-size.test.ts
```
Fail condition: Any measured entry exceeds its checked gzip budget or starts bundling a highlighter.
Fix: Inspect the bundle diff and justify any syntax or dependency cost before adjusting a budget.
## Release Checks
### Check: Run the complete package gate
Expected:
```bash
pnpm run verify
```
Fail condition: Tests, typechecking, build, docs validation, conformance accounting, sizes, benchmarks, or the npm dry run fail.
Fix: Resolve every gate before publishing, including HTML/React/Octane parity; audit raw HTML, highlighter output, HTML hooks, and component replacements separately.
## Common Production Mistakes
### CRITICAL Enabling HTML for untrusted Markdown
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderComment(source: string): string {
return renderHtml(source, { allowHtml: true })
}
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderComment(source: string): string {
return renderHtml(source)
}
```
`allowHtml` emits raw nodes and is not a sanitization step.
Source: `docs/core-concepts/security.md`
### HIGH Returning highlighter containers
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
function escapeCode(code: string): string {
return code.replace(/[&<>]/g, (character) => ({
'&': '&',
'<': '<',
'>': '>',
})[character] ?? character)
}
console.log(renderHtml('```ts\nconst x = 1\n```', {
highlighter: (code) => `<pre><code>${escapeCode(code)}</code></pre>`,
}))
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
function escapeCode(code: string): string {
return code.replace(/[&<>]/g, (character) => ({
'&': '&',
'<': '<',
'>': '>',
})[character] ?? character)
}
console.log(renderHtml('```ts\nconst x = 1\n```', {
highlighter: (code) => `<span class="token">${escapeCode(code)}</span>`,
}))
```
The renderer owns `<pre><code>`; the callback supplies only the code element's trusted contents.
Source: `docs/guides/syntax-highlighting.md`
### CRITICAL Trusting arbitrary highlighter output
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
const source = '```html\n<img src=x onerror=alert(1)>\n```'
console.log(renderHtml(source, { highlighter: (code) => code }))
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
function escapeCode(code: string): string {
return code.replace(/[&<>]/g, (character) => ({
'&': '&',
'<': '<',
'>': '>',
})[character] ?? character)
}
const source = '```html\n<img src=x onerror=alert(1)>\n```'
console.log(renderHtml(source, { highlighter: escapeCode }))
```
Highlighter output is inserted without further escaping in every renderer.
Source: `docs/core-concepts/security.md`
### MEDIUM Bundling highlighting into static clients
Wrong:
```tsx
import { Markdown } from '@tanstack/markdown/react'
import { tokenize } from '@tanstack/highlight'
export function Article({ source }: { source: string }) {
return <Markdown highlighter={(code) => String(tokenize(code))}>{source}</Markdown>
}
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
import type { CodeHighlighter } from '@tanstack/markdown'
export function renderStaticArticle(
source: string,
highlighter: CodeHighlighter,
): string {
return renderHtml(source, { highlighter })
}
```
Tokenizer runtimes, grammars, and themes can outweigh Markdown parsing and should remain build-time or server-side for static content.
Source: `docs/guides/performance.md`
### CRITICAL Treating defaults as a sanitizer
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderForEveryPolicy(source: string): string {
return renderHtml(source)
}
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderWithPolicy(
source: string,
sanitize: (html: string) => string,
): string {
return sanitize(renderHtml(source))
}
```
Core escaping and protocol filtering do not enforce application-specific outbound-link, image, or final-HTML policy.
Source: `docs/core-concepts/security.md`
### HIGH Assuming complete CommonMark behavior
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderArbitraryMarkdown(source: string): string {
return renderHtml(source)
}
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderControlledDocs(source: string): string {
return renderHtml(source)
}
```
The package implements a documented docs/blog profile, not complete CommonMark, GFM, MDX, or arbitrary plugin behavior.
Source: `docs/core-concepts/syntax-profile.md`
### MEDIUM Reparsing unchanged content
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
export function renderRequest(source: string): string {
return renderHtml(source)
}
```
Correct:
```ts
import type { MarkdownDocument } from '@tanstack/markdown'
import { renderHtml } from '@tanstack/markdown/html'
import { parseMarkdown } from '@tanstack/markdown/parser'
export function compile(source: string): MarkdownDocument {
return parseMarkdown(source)
}
export function renderRequest(document: MarkdownDocument): string {
return renderHtml(document)
}
```
String render inputs parse the complete document, while a cached `MarkdownDocument` skips that work.
Source: `docs/core-concepts/document-model.md`
### HIGH Injecting HTML into React
Wrong:
```tsx
import { renderHtml } from '@tanstack/markdown/html'
export function Article({ source }: { source: string }) {
const html = renderHtml(source)
return <article dangerouslySetInnerHTML={{ __html: html }} />
}
```
Correct:
```tsx
import { Markdown } from '@tanstack/markdown/react'
export function Article({ source }: { source: string }) {
return <article><Markdown>{source}</Markdown></article>
}
```
The HTML string adds a trusted insertion boundary and bypasses React component replacement.
Source: `docs/guides/react.md`
### MEDIUM Expecting fence metadata to highlight
Wrong:
```ts
import { renderHtml } from '@tanstack/markdown/html'
const source = '```ts {1}\nconst answer = 42\n```'
console.log(renderHtml(source))
```
Correct:
```ts
import { renderHtml } from '@tanstack/markdown/html'
function escapeCode(code: string): string {
return code.replace(/[&<>]/g, (character) => ({
'&': '&',
'<': '<',
'>': '>',
})[character] ?? character)
}
const source = '```ts {1}\nconst answer = 42\n```'
console.log(renderHtml(source, { highlighter: escapeCode }))
```
Fence metadata enters the AST, but token markup requires an external highlighter.
Source: `docs/core-concepts/syntax-profile.md`
## Tensions
### Compatibility breadth versus bundle budget
Do not maximize conformance by default. Require target-corpus evidence, renderer coverage, and measured bundle cost before adding syntax.
### Rich trusted output versus untrusted-content safety
Do not enable raw HTML, extension HTML, or highlighter markup globally to solve presentation needs. Scope each trusted callback to controlled content.
### Parse-ahead performance versus option timing
Build cached ASTs with final parser options and document transforms. Keep required HTML render hooks active when rendering those cached documents.
## Pre-Deploy Summary
- [ ] Every content source is classified as trusted or untrusted.
- [ ] Raw HTML, extension HTML, and highlighter output have explicit owners.
- [ ] Application link, image, and final-sanitization policies are enforced.
- [ ] The downstream corpus passes deterministic AST and renderer checks.
- [ ] Unsupported syntax is documented rather than silently assumed.
- [ ] Stable content is parsed once with final options and versioned cache keys.
- [ ] Highlighting runs at build time or on the server where possible.
- [ ] Narrow entry points and individual extensions are used.
- [ ] Bundle budgets and HTML/React/Octane parity tests pass.
- [ ] `pnpm run verify` passes before release.
## Related Skills
- `render-markdown` for the supported profile, AST, parser options, and core HTML rendering.
- `docs-features` for docs metadata and code-fence behavior.
- `custom-extensions` for parser hooks and trusted HTML extension boundaries.
- `react-rendering` and `octane-rendering` for framework component policy and SSR parity.