How to Use next‑mdx‑remote to Render MDX Content in Next.js
next‑mdx‑remote serializes MDX source into React-compatible code at build time, enabling you to load Markdown files dynamically and render them with custom components using a plugin-based processing pipeline.
The woosal1337/blog repository implements a complete next‑mdx‑remote workflow that decouples content from the Next.js build process. This approach allows the blog to store articles as static .mdx files while applying custom remark/rehype plugins and Tailwind-styled components for maximum rendering flexibility.
Why Use next‑mdx‑remote?
Unlike Next.js built-in MDX support, next‑mdx‑remote lets you fetch content from external sources or local filesystems without bundling MDX files through the webpack loader. In the woosal1337/blog repository, this pattern enables granular control over the compilation process—specifically through centralized options in lib/mdx-options.mjs and a custom component registry in mdx-components.tsx.
Configuring MDX Processing Options
The repository centralizes its MDX configuration in lib/mdx-options.mjs. This file exports an mdxOptions object that configures remark and rehype plugins for GitHub-flavored Markdown and syntax highlighting.
// lib/mdx-options.mjs
import remarkGfm from 'remark-gfm';
import rehypePrettyCode from 'rehype-pretty-code';
export const mdxOptions = {
remarkPlugins: [remarkGfm],
rehypePlugins: [
[
rehypePrettyCode,
{
theme: { light: 'github-light', dark: 'github-dark' },
keepBackground: false,
},
],
],
};
This options object gets passed to the serialize function to ensure consistent processing across all MDX content.
Serializing MDX Content with next‑mdx‑remote
To convert raw MDX strings into renderable React code, the repository uses the serialize function from next‑mdx‑remote/serialize. In lib/blog.ts, a helper function reads the .mdx file and processes it through the serialization pipeline.
// lib/blog.ts
import { readFile } from 'fs/promises';
import { join } from 'path';
import { serialize } from 'next-mdx-remote/serialize';
import { mdxOptions } from './mdx-options.mjs';
export async function getPost(slug: string) {
const filePath = join(process.cwd(), 'app/(website)/blog', `${slug}.mdx`);
const mdxString = await readFile(filePath, 'utf-8');
const source = await serialize(mdxString, { mdxOptions });
return { source };
}
The resulting source object contains the compiled component code and metadata required by the MDXRemote renderer.
Defining Custom Components for Rendering
Before rendering, the repository defines a component mapping that aligns with its dark-only design system. The mdx-components.tsx file exports a useMDXComponents hook that merges default elements with custom primitives like styled headings and code blocks.
// mdx-components.tsx
export function useMDXComponents(components: any) {
return {
h1: (props: any) => <h1 className="text-3xl font-bold text-white" {...props} />,
pre: (props: any) => <pre className="rounded-lg bg-slate-900 p-4" {...props} />,
...components,
};
}
This mapping ensures that standard Markdown elements (headings, lists, code blocks) render with the site's specific Tailwind classes.
Rendering MDX in Next.js Pages
Finally, the repository renders the serialized content using the MDXRemote component from next‑mdx‑remote. In app/(website)/blog/[slug]/page.tsx, a server component fetches the serialized data and injects the custom component map.
// app/(website)/blog/[slug]/page.tsx
import { getPost } from '@/lib/blog';
import { MDXRemote } from 'next-mdx-remote';
import { useMDXComponents } from '@/mdx-components';
export default async function Page({ params }: { params: { slug: string } }) {
const { source } = await getPost(params.slug);
const components = useMDXComponents({});
return (
<article className="prose prose-dark mx-auto">
<MDXRemote {...source} components={components} />
</article>
);
}
Because next‑mdx‑remote version ^6.0.0 supports React Server Components, this pattern works seamlessly with the App Router without requiring client-side hydration wrappers.
Summary
lib/mdx-options.mjscentralizes remark and rehype plugin configuration for consistent MDX processing.serializefromnext‑mdx‑remote/serializecompiles raw MDX strings into renderable objects at build time.mdx-components.tsxprovides auseMDXComponentshook that maps Markdown elements to Tailwind-styled React components.MDXRemoteaccepts the serialized source and component map to render JSX-enhanced Markdown in Next.js pages.- The woosal1337/blog implementation uses
next‑mdx‑remoteversion^6.0.0with the App Router for optimal static site generation.
Frequently Asked Questions
Can next‑mdx‑remote be used with the Next.js App Router?
Yes. Version ^6.0.0 of next‑mdx‑remote fully supports React Server Components. You can call serialize in server components and render <MDXRemote> directly without "use client" directives, as demonstrated in the app/(website)/blog/[slug]/page.tsx implementation.
How does serialization differ from traditional MDX loading?
Traditional MDX loaders bundle files at build time through webpack, limiting dynamic content sourcing. The serialize function processes MDX strings on-demand, enabling you to fetch content from CMSs, databases, or local filesystems while still applying custom plugins and component mappings.
What plugins work with next‑mdx‑remote?
Any remark or rehype plugin compatible with the unified ecosystem works with next‑mdx‑remote. The woosal1337/blog repository uses remark-gfm for GitHub-flavored Markdown tables and rehype-pretty-code for Shiki-powered syntax highlighting, configured through the mdxOptions object.
Does MDXRemote require client-side JavaScript?
No. When used with Server Components in Next.js, MDXRemote renders static HTML at build time. The component only hydrates if you pass interactive client components in your components map. For static content like blog posts, the rendered output requires zero client-side JavaScript.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →