Introducing TanStack Markdown and TanStack Highlight | TanStack Blog*]:transition-opacity [&>*]:duration-1000"><br>TanStack
SearchAIAsk AILog In
Log In
BlogOn this page
Introducing TanStack Markdown and TanStack Highlight<br>Copy page
by Tanner Linsley on Jul 24, 2026.
Our last post about removing React Server Components from tanstack.com ended with a pretty simple preference, I don't want to reach for an architecture to hide a dependency problem, I'd rather make the dependency small and let content be content.<br>TanStack Markdown and TanStack Highlight are the two libraries that made that decision possible, and we haven't properly introduced either of them yet.<br>We didn't set out to build two more TanStack libraries, we were trying to make one embarrassingly large part of tanstack.com small enough that we could stop designing the site around it. Earlier this year, a docs page was transferring about 1.1 MiB of script, with roughly 358 KiB tied to syntax highlighting alone. Shiki, its WASM and runtime pieces, themes, language chunks, and our Markdown pipeline had quietly turned reading a page into downloading a small publishing system.<br>RSC gave us a good way to keep that system on the server, and it worked, but the underlying cost was still there. We'd made an application architecture decision around hiding a dependency problem, and I couldn't stop asking why rendering the subset of Markdown and web code we actually use needed to be that expensive in the first place.<br>So we made the expensive part small.<br>Today we're releasing the first alphas of TanStack Markdown and TanStack Highlight, two deliberately narrow libraries built around the content contract we actually need for technical blogs, documentation, and streamed AI output. The last post was about the architecture we could remove, this one is about what we built to remove it.<br>Parsing and highlighting are different jobs#<br>Markdown and syntax highlighting tend to arrive as one tangled pipeline. A parser finds a code fence, a plugin loads a grammar engine, the grammar engine loads themes, the renderer turns the whole thing into framework-specific output, and pretty soon changing how a link works means understanding half of a compiler toolchain.<br>TanStack Markdown parses content into a public, serializable document tree. TanStack Highlight turns known source languages into escaped, semantic HTML. Neither package imports the other, and the boundary between them is a normal callback carrying code, language, and fence metadata.<br>That separation is doing a lot of work for us. A Markdown document can be parsed, cached, indexed, inspected, or rendered without loading a highlighter, and a highlighter can be used for a code example that has never been near Markdown. If you connect them, you choose the language set and theme contract explicitly.<br>The Markdown AST is ordinary data#<br>Most Markdown APIs make the rendered output the product. TanStack Markdown treats the parsed document as the durable part.<br>ts
import { parseMarkdown } from '@tanstack/markdown/parser'<br>import { renderHtml } from '@tanstack/markdown/html'
const document = parseMarkdown(source)<br>const cached = JSON.stringify(document)<br>const html = renderHtml(document)
The tree is plain objects and arrays, so it can cross a server boundary, sit in a cache, feed a search index, or render later through HTML, React, or Octane. Parsing doesn't trap the content inside a framework or an async plugin pipeline.<br>tsx
import { Markdown } from '@tanstack/markdown/react'
export function Article({ source }: { source: string }) {<br>return Markdown>{source}Markdown>
The parser is currently 4.9 KB gzipped , the HTML renderer and framework adapters are about 6.7 KB gzipped , and the package has zero runtime dependencies. React and Octane are optional peers behind their own entry points, while docs extensions, callouts, streaming behavior, and other transforms stay in separate imports.<br>That size comes from having an opinion about the job. TanStack Markdown supports the syntax we use across technical blogs and documentation, including headings, emphasis, lists, task lists, tables, footnotes, fenced code and metadata, links, images, references, and optional frontmatter. It does not promise every CommonMark edge case, arbitrary async plugin chains, automatic linkification, MDX evaluation, or a bundled sanitizer.<br>Raw HTML is escaped by default and executable URL protocols are removed, but this is still a rendering primitive, not a complete policy for untrusted content. Applications still own outbound link behavior, remote images, and what happens if they explicitly enable raw HTML.<br>Stream the text, not parser state#<br>AI output made another Markdown tradeoff feel more complicated than it needed to be. A lot of streaming renderers maintain incremental parser state as tokens arrive, which means the state has to survive updates, recover from partial syntax, and eventually agree with the completed document.<br>TanStack Markdown's optional...