react-render-to-markdown

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.
Installation
The major version of react-render-to-markdown follows the React version. Install the one that matches your project:
npm install react-render-to-markdown@19
npm install react-render-to-markdown@18
Quick Start
import { renderToMarkdownString } from 'react-render-to-markdown';
const markdown = await renderToMarkdownString(<h1>Hello, World!</h1>);
console.log(markdown);
Usage
Basic HTML Elements
import { renderToMarkdownString } from 'react-render-to-markdown';
await renderToMarkdownString(
<div>
<strong>foo</strong>
<span>bar</span>
</div>,
);
React Components & Hooks
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>,
);
Code Blocks
Fenced 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>,
);
For languages that may contain triple backticks (like markdown, mdx, md), four backticks (``````) are automatically used as delimiters.
Supported Elements
<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 |
<style> | Ignored |
<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.
How It Works
- Custom React Reconciler — Uses
react-reconciler to build a lightweight tree of MarkdownNode objects 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
MarkdownNode tree is serialized to a Markdown string via a recursive toMarkdown function.
Requirements
{
"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).
Used By
- 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.txt and llms-full.txt for Generative Engine Optimization (GEO), enabling better accessibility for AI agents and large language models.
License
MIT