Render React components to Markdown strings — like renderToString in react-dom, but outputs Markdown instead of HTML.
Built on top of react-reconciler, this library creates a custom React renderer that traverses the React element tree and produces well-formatted Markdown. It follows SSR-like behavior: useEffect, useLayoutEffect, and useInsertionEffect are suppressed (as no-ops), while useState, useMemo, useRef, useContext, and other synchronous hooks work as expected.
The major version of react-render-to-markdown follows the React version. Install the one that matches your project:
# React 19
npm install react-render-to-markdown@19
# React 18
npm install react-render-to-markdown@18import { renderToMarkdownString } from 'react-render-to-markdown';
const markdown = await renderToMarkdownString(<h1>Hello, World!</h1>);
console.log(markdown); // # Hello, World!import { renderToMarkdownString } from 'react-render-to-markdown';
await renderToMarkdownString(
<div>
<strong>foo</strong>
<span>bar</span>
</div>,
);
// Output: '**foo**bar'Synchronous hooks (useState, useMemo, useRef, useContext, etc.) work as expected. Client-side effects (useEffect, useLayoutEffect) are automatically suppressed:
import { createContext, useContext, useMemo, useState } from 'react';
import { renderToMarkdownString } from 'react-render-to-markdown';
const ThemeContext = createContext('light');
const Article = () => {
const [count] = useState(42);
const theme = useContext(ThemeContext);
const doubled = useMemo(() => count * 2, [count]);
return (
<>
<h1>Hello World</h1>
<p>Count: {count}, Doubled: {doubled}, Theme: {theme}</p>
</>
);
};
await renderToMarkdownString(
<ThemeContext.Provider value="dark">
<Article />
</ThemeContext.Provider>,
);
// Output:
// # Hello World
//
// Count: 42, Doubled: 84, Theme: darkFenced code blocks with language and title support:
await renderToMarkdownString(
<pre data-lang="ts" data-title="rspress.config.ts">
<code>{'const a = 1;\n'}</code>
</pre>,
);
// Output:
// ```ts title=rspress.config.ts
// const a = 1;
// ```For languages that may contain triple backticks (like markdown, mdx, md), four backticks (``````) are automatically used as delimiters.
| HTML Element | Markdown Output |
|---|---|
<h1> – <h6> |
# – ###### headings |
<p> |
Paragraph with trailing newlines |
<strong>, <b> |
**bold** |
<em>, <i> |
*italic* |
<code> |
`inline code` |
<pre> + <code> |
Fenced code block (```) |
<a href=""> |
[text](url) |
<img> |
 |
<ul>, <ol>, <li> |
Unordered / ordered lists |
<blockquote> |
> blockquote |
<br> |
Line break |
<hr> |
--- horizontal rule |
<table>, <thead>, <tbody>, <tr>, <th>, <td> |
GFM table |
Any unrecognized elements (e.g. <div>, <span>, <section>) render their children as-is, acting as transparent wrappers.
- Custom React Reconciler — Uses
react-reconcilerto build a lightweight tree ofMarkdownNodeobjects from your React element tree. - SSR-like Hook Behavior — Client-side effects (
useEffect,useLayoutEffect,useInsertionEffect) are intercepted and turned into no-ops, matching React's Fizz server renderer behavior. This ensures browser-only code (e.g.document,window) in effects never runs. - Tree-to-Markdown Serialization — The
MarkdownNodetree is serialized to a Markdown string via a recursivetoMarkdownfunction.
{
"react": ">=19.0.0",
"react-reconciler": "^0.33.0"
}Note: React 19 or above is required. The effect-interception mechanism relies on React 19's internal hooks dispatcher (
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.H).
- Rspress SSG-MD — Rspress uses this library to power its SSG-MD (Static Site Generation to Markdown) feature. SSG-MD renders documentation pages as Markdown files instead of HTML, generating
llms.txtandllms-full.txtfor Generative Engine Optimization (GEO), enabling better accessibility for AI agents and large language models.