@clo/react-markdownThis package exports a React component to render Markdown using the unified
ecosystem's Markdown tools (remark, rehype). The exported <Markdown />
component is extremely memoized, making it suitable for streaming situations
such as LLM chat interfaces.
unified.Processor with remark and rehype plugins configured.
Additionally, you can override the element renderers with the components
attribute. By default, @clo/react-markdown applies a bare bones config so
you can get started with just <Markdown content="hi" />. Configuration is
done either with a provider, or right at the component level.predict prop is set, the document
will be treated as an incomplete stream at the end of a document.
hello **world will be emitted as hello <strong>world</strong>, perfect for
the LLM case. (standalone predictor exported as @clo/react-markdown/Predict)The motivation for this package is to have an easy to understand version of Streamdown. With me banning all Vercel software at the company I work at, we needed a simple, trustable solution for this problem. Some differences to Streamdown:
<a> tags or other components. (For example, if a custom <a> fetches
previewing data and provides a hover card, that card won't flicker).@clo/react-markdown simply takes in
your existing unified pipeline and works off of that.Install off of the JSR package:
npx jsr add @clo/react-markdown
pnpm add jsr:@clo/react-markdown
import { Markdown } from "@clo/react-markdown";
import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import { unified } from "unified";
// Define any unified processing pipeline. We expect that your
// app already has one of these. @clo/react-markdown works with
// most pipelines, but some important things to note:
// - During parsing, all nodes have to have a source location.
// - The pipeline must output a remark (Markdown) AST.
// - You can also add `remarkRehype` and some rehype plugins.
// - Do not include the `rehypeReact` plugin in here. It is added for you.
const processor = unified().use(remarkParse).use(remarkGfm);
// Define custom components. (maps to rehypeReact's option)
const components = {
a: MarkdownLink,
code: InlineCode,
};
export function HelloWorld() {
return (
<Markdown
content="hello **world"
// Predict closing tags for streaming use case
predict
// These are optional, and can be set via `MarkdownOptionsProvider`
processor={processor}
components={components}
/>
);
}
If you want to play with the example project, which offers a comparison to
Streamdown and the non-memoized react-markdown package.
git clone https://git.paperclover.net/clo/react-markdown
cd react-markdown
pnpm demo # auto-install
The Markdown component is built out of a memoizer which uses the following tricks to improve performance:
React.memo() calls. Obviously.All put together, basically nothing rerenders except what actually changed, and the document is beautiful.