TanStack
TanStack

Markdown

alpha

Markdown with an exit strategy.

Parse documents or accumulated AI output into a plain, serializable tree. Inspect it, cache it, index it, or render it as HTML, React, or Octane.

Docs
article.md
---
title: Ship the docs
---

# One source

Parse it **once**. Render it where the product needs it.

- cache the tree
- index the text
- choose a renderer
root · 3 blocks
frontmatter · string
heading · depth: 1
paragraph · 3 inline nodes
list · 3 items

The AST is the product.

Parsing does not trap content inside a renderer. Edit the source and inspect the serializable tree, deterministic HTML, or React output.

Parsing locally
article.mdeditable source
{
  "type": "root",
  "frontmatter": "title: Durable content",
  "children": [
    {
      "type": "heading",
      "depth": 1,
      "id": "one-source-many-destinations",
      "children": [
        {
          "type": "text",
          "value": "One source, many destinations"
        }
      ]
    },
    {
      "type": "paragraph",
      "children": [
        {
          "type": "text",
          "value": "Parse the document "
        },
        {
          "type": "strong",
          "children": [
            {
              "type": "text",
              "value": "once"
            }
          ]
        },
        {
          "type": "text",
          "value": ", then keep the tree."
        }
      ]
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        {
          "type": "listItem",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "type": "text",
                  "value": "cache it at the edge"
                }
              ]
            }
          ]
        },
        {
          "type": "listItem",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "type": "text",
                  "value": "index its text"
                }
              ]
            }
          ]
        },
        {
          "type": "listItem",
          "children": [
            {
              "type": "paragraph",
              "children": [
                {
                  "type": "text",
                  "value": "render it with React or HTML"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "type": "blockquote",
      "children": [
        {
          "type": "paragraph",
          "children": [
            {
              "type": "text",
              "value": "The renderer can change. The document does not."
            }
          ]
        }
      ]
    }
  ]
}

Stream the text. Keep the parser stateless.

Append each chunk and pass the complete string through Markdown. The optional streaming profile reparses synchronously, with no incremental state to coordinate or recover.

Complete
Accumulated source
# Streaming response

The model can send **ordinary Markdown** as it thinks.

- completed blocks stay stable
- unfinished markers stay out of the way
- React and HTML stay in sync

```ts
const text = responseSoFar
return render(text)
```

[Unsafe links stay text-only](javascript:alert("nope"))
React output

Streaming response

The model can send ordinary Markdown as it thinks.

  • completed blocks stay stable
  • unfinished markers stay out of the way
  • React and HTML stay in sync
const text = responseSoFar
return render(text)

Unsafe links stay text-only

Complete input, reparsed294 / 294 chars

It does less Markdown on purpose.

Technical docs need a known vocabulary, not an open-ended compiler platform. New syntax has to justify its bytes, ambiguity, and maintenance cost.

Inside the profile
  • Headings and emphasis
  • Lists and task lists
  • Tables and footnotes
  • Fenced code with metadata
  • Links, images, and references
  • Frontmatter
Outside the contract
  • Every CommonMark edge case
  • Arbitrary async plugin chains
  • MDX or JSX evaluation
  • A bundled highlighter
  • A general HTML sanitizer

Safe defaults for Markdown input.

Markdown source renders with raw HTML escaped and unsafe link and image URLs removed by default. Custom ASTs, highlighters, and HTML hooks are trusted application inputs.

Untrusted input
<script>alert("not today")</script>

[Run code](javascript:alert("nope"))

**Trusted Markdown still renders.**
Deterministic HTML
<p>&lt;script&gt;alert(&quot;not today&quot;)&lt;/script&gt;</p>
<p>Run code</p>
<p><strong>Trusted Markdown still renders.</strong></p>
HTML escapedExecutable URL removedMarkdown preserved

A parser should not outweigh the page.

Split entry points keep the parser, renderers, framework adapters, and docs extensions independent. Import only the layer the page needs.

TanStack HTML6.7 KB
marked12.5 KB
unified stack36.8 KB
markdown-it52.7 KB
4.9 KB
Parser
6.6 KB
React adapter
6.6 KB
Octane adapter
2.3 KB
docs preset

Gzip sizes for minified browser bundles. Renderers include the parser; framework runtimes and highlighters are excluded. Feature sets differ. Measurement details

Syntax highlighting stays outside the parser.

Code fences store language and metadata in the AST. Supply a highlighter callback to render escaped token markup inside Markdown's code containers.

Companion, not dependency

TanStackHighlight

Synchronous highlighting for the code fences the document model already understands.

Explore Highlight

Partners

Gold
CodeRabbit
Cloudflare
Netlify
Railway
Render
Vercel
Lovable
Silver
WorkOS
AG Grid
OpenRouter
Clerk
SerpApi
Bronze
Sentry
Electric
Unkey
Prisma
OSS Sponsors

Sponsors get special perks like private discord channels, priority issue requests, and direct support!