@temelj/mdx-react
React utilities for rendering compiled MDX content with support for custom components and async rendering.
Installation
npm install @temelj/mdx-react @temelj/mdx
Overview
The @temelj/mdx-react package provides utilities to:
Render compiled MDX artifacts as React components
Provide custom components to MDX content
Support async MDX content rendering
Type-safe frontmatter and scope integration
Quick start
import { MdxCompiler } from "@temelj/mdx" ;
import { createMdxContent } from "@temelj/mdx-react" ;
import React from "react" ;
const compiler = new MdxCompiler ();
const artifact = await compiler . compile ( `
# Hello World
This is **MDX** content.
` );
const content = createMdxContent ({ artifact });
// Render in your React app
function App () {
return < div >{ content } </ div > ;
}
createMdxContent
Render synchronous MDX content as React elements:
function createMdxContent < TFrontmatter , TScope >(
options : MdxContentOptions < TFrontmatter , TScope >,
components ?: MdxContentComponents
) : React . ReactNode ;
Basic usage
import { MdxCompiler } from "@temelj/mdx" ;
import { createMdxContent } from "@temelj/mdx-react" ;
const compiler = new MdxCompiler ();
const artifact = await compiler . compile ( `
# Welcome
Hello from MDX!
` );
const content = createMdxContent ({ artifact });
// Use in React component
function Page () {
return (
< article >
{ content }
</ article >
);
}
With custom components
Provide custom React components to replace default HTML elements:
import { MdxCompiler } from "@temelj/mdx" ;
import { createMdxContent } from "@temelj/mdx-react" ;
const compiler = new MdxCompiler ();
const artifact = await compiler . compile ( `
# Custom Heading
This is a [custom link](https://example.com).
` );
const components = {
h1 : ({ children }) => (
< h1 className = "text-4xl font-bold text-blue-600" >
{ children }
</ h1 >
),
a : ({ href , children }) => (
< a href = { href } className = "text-blue-500 hover:underline" >
{ children }
</ a >
),
};
const content = createMdxContent ({ artifact }, components );
function Page () {
return < article >{ content } </ article > ;
}
You can override any HTML element or provide custom components for MDX-specific elements.
createAsyncMdxContent
Render async MDX content that may contain dynamic imports:
function createAsyncMdxContent < TFrontmatter , TScope >(
options : MdxContentOptions < TFrontmatter , TScope >,
components ?: MdxContentComponents
) : Promise < React . ReactNode >;
Usage with async content
import { MdxCompiler } from "@temelj/mdx" ;
import { createAsyncMdxContent } from "@temelj/mdx-react" ;
import { Suspense } from "react" ;
const compiler = new MdxCompiler ();
const artifact = await compiler . compile ( `
# Async Content
This content may use dynamic imports.
` );
async function loadContent () {
return await createAsyncMdxContent ({ artifact });
}
function Page () {
const [ content , setContent ] = React . useState ( null );
React . useEffect (() => {
loadContent (). then ( setContent );
}, []);
if ( ! content ) return < div > Loading ...</ div > ;
return < article >{ content } </ article > ;
}
Use createAsyncMdxContent when your MDX content uses dynamic imports or async operations. For most cases, createMdxContent is sufficient.
MdxContentOptions
Configuration options for rendering MDX content:
interface MdxContentOptions < TFrontmatter , TScope > {
// The compiled MDX artifact from MdxCompiler
artifact : MdxArtifact < TFrontmatter >;
// Additional variables to inject into MDX scope
scope ?: TScope ;
// Base URL for relative imports
importBaseUrl ?: string ;
}
Injecting scope variables
Provide custom variables to your MDX content:
import { MdxCompiler } from "@temelj/mdx" ;
import { createMdxContent } from "@temelj/mdx-react" ;
const compiler = new MdxCompiler ();
const artifact = await compiler . compile ( `
# Hello {name}
You have {count} notifications.
` );
const content = createMdxContent ({
artifact ,
scope: {
name: "John" ,
count: 5 ,
},
});
// Renders: "Hello John" and "You have 5 notifications."
Setting import base URL
Configure the base URL for resolving relative imports:
import { createMdxContent } from "@temelj/mdx-react" ;
const content = createMdxContent ({
artifact ,
importBaseUrl: "/content/" ,
});
The importBaseUrl defaults to "/". Set it explicitly if your MDX content uses relative imports from a different base path.
Custom components
The MdxContentComponents type allows you to override any MDX element:
import type { MdxContentComponents } from "@temelj/mdx-react" ;
const components : MdxContentComponents = {
// HTML elements
h1 : ({ children }) => < h1 className = "title" >{ children } </ h1 > ,
h2 : ({ children }) => < h2 className = "subtitle" >{ children } </ h2 > ,
p : ({ children }) => < p className = "paragraph" >{ children } </ p > ,
a : ({ href , children }) => (
< a href = { href } className = "link" target = "_blank" rel = "noopener" >
{ children }
</ a >
),
// Code blocks
pre : ({ children }) => (
< pre className = "code-block" > { children } </ pre >
),
code : ({ children , className }) => (
< code className = { className } > { children } </ code >
),
// Lists
ul : ({ children }) => < ul className = "list-disc ml-6" >{ children } </ ul > ,
ol : ({ children }) => < ol className = "list-decimal ml-6" >{ children } </ ol > ,
li : ({ children }) => < li className = "mb-2" >{ children } </ li > ,
// Custom components
Button : ({ children , onClick }) => (
< button onClick = { onClick } className = "btn" >
{ children }
</ button >
),
};
Using custom components in MDX
Once you define custom components, use them in your MDX:
# My Document
This is a paragraph with a [ link ]( https://example.com ).
< Button onClick = {() = > alert('Clicked!')}>Click Me </ Button >
```javascript
const code = "highlighted";
```
Working with frontmatter
Access typed frontmatter in your React components:
import { MdxCompiler } from "@temelj/mdx" ;
import { createMdxContent } from "@temelj/mdx-react" ;
import { z } from "zod" ;
const frontmatterSchema = z . object ({
title: z . string (),
author: z . string (),
publishedAt: z . coerce . date (),
});
const compiler = new MdxCompiler ();
const artifact = await compiler . compile (
`
---
title: My Article
author: John Doe
publishedAt: 2024-01-01
---
# Content here
` ,
{},
frontmatterSchema
);
function Article () {
const content = createMdxContent ({ artifact });
const { title , author , publishedAt } = artifact . frontmatter ;
return (
< article >
< header >
< h1 >{ title } </ h1 >
< p > By { author } </ p >
< time dateTime = {publishedAt.toISOString()} >
{ publishedAt . toLocaleDateString ()}
</ time >
</ header >
< main >{ content } </ main >
</ article >
);
}
Complete example
A comprehensive example showing compilation, custom components, and rendering:
Compiler setup
React components
import { MdxCompiler , syntaxHighlightPlugin } from "@temelj/mdx" ;
import { createMdxContent , type MdxContentComponents } from "@temelj/mdx-react" ;
import { z } from "zod" ;
// Configure compiler
const compiler = new MdxCompiler ()
. withRehypePlugin ( syntaxHighlightPlugin , {
includeDataAttributes: [ "language" ],
lineNumbers: { className: "line-number" },
});
// Define frontmatter schema
const schema = z . object ({
title: z . string (),
description: z . string (),
tags: z . array ( z . string ()). default ([]),
});
// Compile MDX
const artifact = await compiler . compile (
`
---
title: Complete Example
description: A full MDX React example
tags: ["react", "mdx"]
---
# {frontmatter.title}
{frontmatter.description}
<CustomCard title="Info">
This is a custom component!
</CustomCard>
\`\`\` typescript {"showLineNumbers":true}
const example = "code";
\`\`\`
` ,
{},
schema
);
Type exports
export function createMdxContent <
TFrontmatter = Record < string , unknown >,
TScope = unknown
>(
options : MdxContentOptions < TFrontmatter , TScope >,
components ?: MdxContentComponents
) : React . ReactNode ;
export function createAsyncMdxContent <
TFrontmatter = Record < string , unknown >,
TScope = unknown
>(
options : MdxContentOptions < TFrontmatter , TScope >,
components ?: MdxContentComponents
) : Promise < React . ReactNode >;
interface MdxContentOptions < TFrontmatter , TScope > {
artifact : MdxArtifact < TFrontmatter >;
scope ?: TScope ;
importBaseUrl ?: string ;
}
export type MdxContentComponents = React . ComponentProps <
typeof MdxProvider
>[ "components" ];
export type { MdxProvider };
Integration with Next.js
Use with Next.js App Router:
// app/blog/[slug]/page.tsx
import { MdxCompiler , syntaxHighlightPlugin } from "@temelj/mdx" ;
import { createMdxContent } from "@temelj/mdx-react" ;
import { readFile } from "fs/promises" ;
import { z } from "zod" ;
const frontmatterSchema = z . object ({
title: z . string (),
description: z . string (),
});
const compiler = new MdxCompiler (). withRehypePlugin ( syntaxHighlightPlugin );
interface PageProps {
params : { slug : string };
}
export default async function BlogPage ({ params } : PageProps ) {
const source = await readFile ( `./content/ ${ params . slug } .mdx` , "utf-8" );
const artifact = await compiler . compile ( source , {}, frontmatterSchema );
const content = createMdxContent ({ artifact });
return (
< article >
< h1 >{artifact.frontmatter. title } </ h1 >
< p >{artifact.frontmatter. description } </ p >
{ content }
</ article >
);
}
The package works seamlessly with React Server Components in Next.js 13+.