Skip to content

Instantly share code, notes, and snippets.

Last active Jun 15, 2022
What would you like to do?
How to process markdown without async code to make it work with server-side React and common-js (e.g. in
* DEPRECATION NOTICE: This workaround is no longer needed. You can now use ESM packages with Remix by
* tracking all ESM packages in your remix.config.js file. This means we can now use react-markdown
* and are not required to reimplement everything from scratch.
* Find a nice example implementation here:
* react-remark does not support cjs anymore but will not work with esm just yet (without async imports)
* Unfortunately, the older versions of react-remark do not implement useRemarkSync
* Thus, right now there is no way to use react-remark in remix to render markdown server-side
* since it will always require async code (and require a useEffect)
* My solution: Using old versions of remark, unified, and rehype directly to basically re-implement react-remark
import { FC, ReactElement, useMemo } from 'react';
import { Fragment, createElement } from 'react';
import unified from 'unified';
import remarkParse from 'remark-parse';
import remarkToRehype from 'remark-rehype';
import rehypeReact, { Options as RehypeReactOptions } from 'rehype-react';
// My custom components: Replace this with your own custom components or delete it
import H1 from './h1';
import H2 from './h2';
import H3 from './h3';
import H4 from './h4';
import Paragraph from './p';
import OrderedList from './ol';
import UnorderedList from './ul';
import CodeBlock from './pre';
import DocLink from './link';
import ListItem from './li';
import DocImage from './img';
import HorizontalLine from './hr';
import Decoder from './decoder';
import Blockquote from './blockquote';
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type ReactOptions = PartialBy<RehypeReactOptions<typeof createElement>, 'createElement'>;
export const useRemarkSync = (source: string, rehypeReactOptions: ReactOptions): React.ReactElement =>
() =>
.use(rehypeReact, {
} as RehypeReactOptions<typeof createElement>)
.processSync(source).result as React.ReactElement,
[source, rehypeReactOptions],
interface MarkdownContainerProps {
source: string; // The markdown string
const MarkdownContainer: FC<MarkdownContainerProps> = ({ source }) => {
const html = useRemarkSync(source, {
// Optional: a mapping of html tags to custom React components
components: {
h1: H1,
h2: H2,
h3: H3,
h4: H4,
p: Paragraph,
ol: OrderedList,
ul: UnorderedList,
li: ListItem,
pre: CodeBlock,
code: Decoder,
a: DocLink,
img: DocImage,
hr: HorizontalLine,
blockquote: Blockquote,
return html;
export default MarkdownContainer;
"rehype-react": "^6.2.1",
"remark-parse": "^9.0.0",
"remark-rehype": "^8.1.0",
"unified": "^9.2.2"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment