
Skill
production-pipelines
audit and ship production Markdown 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.
SKILL.md
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:
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:
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:
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:
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:
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.12:${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:
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:
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:
import { renderHtml } from '@tanstack/markdown/html'
export function renderComment(source: string): string {
return renderHtml(source, { allowHtml: true })
}
Correct:
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:
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:
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:
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:
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:
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:
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:
import { renderHtml } from '@tanstack/markdown/html'
export function renderForEveryPolicy(source: string): string {
return renderHtml(source)
}
Correct:
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:
import { renderHtml } from '@tanstack/markdown/html'
export function renderArbitraryMarkdown(source: string): string {
return renderHtml(source)
}
Correct:
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:
import { renderHtml } from '@tanstack/markdown/html'
export function renderRequest(source: string): string {
return renderHtml(source)
}
Correct:
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:
import { renderHtml } from '@tanstack/markdown/html'
export function Article({ source }: { source: string }) {
const html = renderHtml(source)
return <article dangerouslySetInnerHTML={{ __html: html }} />
}
Correct:
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:
import { renderHtml } from '@tanstack/markdown/html'
const source = '```ts {1}\nconst answer = 42\n```'
console.log(renderHtml(source))
Correct:
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 verifypasses before release.
Related Skills
render-markdownfor the supported profile, AST, parser options, and core HTML rendering.docs-featuresfor docs metadata and code-fence behavior.custom-extensionsfor parser hooks and trusted HTML extension boundaries.react-renderingandoctane-renderingfor framework component policy and SSR parity.
More skills from the markdown repository
View all 6 skillscustom-extensions
implement custom Markdown rendering extensions
Jul 26HTMLMarkdownPlugin Developmentdocs-features
build documentation features for Markdown
Jul 26DocumentationGitHubMarkdownTechnical Writingoctane-rendering
render Markdown with Octane
Jul 26FrontendMarkdownSSRTanStackreact-rendering
render Markdown in React applications
Jul 26FrontendMarkdownReactSSRrender-markdown
parse and render Markdown documents
Jul 26DocumentationDocumentsHTMLMarkdown
More from TanStack
View publisheraggregation
perform data aggregation in TanStack Table
table
Jul 26Data AnalysisFrontendTanStackapi-not-found
diagnose TanStack Table API errors
table
Jul 26DebuggingFrontendTanStackcell-selection
select rectangular cell ranges in tables
table
Jul 26Data AnalysisTanStackUI Componentsclient-vs-server
manage TanStack Table data pipelines
table
Jul 26Data PipelineFrontendPerformanceTanStackcolumn-faceting
build faceted filter UIs
table
Jul 26Data VisualizationFrontendTanStackcolumn-filtering
implement column filtering in TanStack Table
table
Jul 26Data AnalysisFrontendTanStack