Web Development
How Next.js Processes MDX
By O. Wolfson
MDX (Markdown + JSX) is a powerful format for writing content with React components. Next.js provides seamless support for MDX through the @next/mdx package, allowing developers to write and render MDX content in various ways.
In this article, we will explore how MDX is processed and rendered in Next.js using two examples:
Setting Up Next.js for MDX
To enable MDX in a Next.js app, install the required dependencies:
bashnpm install @next/mdx @mdx-js/loader
Update your next.config.js to handle .mdx files:
javascriptconst withMDX = require("@next/mdx")({
extension: /\.mdx?$/,
});
module.exports = withMDX({
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
});
This configuration ensures that Next.js recognizes .mdx files as valid page or component files.
Example 1: Simple Local MDX Import
This example demonstrates how to render a local .mdx file as part of a React component:
File Structure
/content/test/example.mdx(MDX content file)/examples/simple-mdx(Page rendering the MDX content)
MDX File (example.mdx):
mdx# Hello MDX This is a simple MDX example.
Page Component (/examples/simple-mdx):
javascriptimport Content from "@/content/test/example.mdx";
export default function Page() {
return (
<div className="flex flex-col justify-center items-center h-96 ">
<Content />
</div>
);
}
How It Works
- The MDX file (
example.mdx) is imported as a React component using the@next/mdxloader. - During the build process, the MDX content is compiled into a React component.
- The
Contentcomponent renders the MDX content as part of the React tree. - The custom styles or components defined in your MDX configuration (e.g.,
useMDXComponents) are automatically applied when rendering local MDX.
Example 2: Remote MDX Content
This example demonstrates how to fetch MDX content from a remote source (e.g., a database or API) and dynamically render it.
File Structure
/utils/supabase/client.js(Supabase client configuration)/examples/remote-mdx(Page rendering the remote MDX content)
Supabase Table (mdx_content):
json[
{
"id": 1,
"created_at": "2025-01-08T21:50:22.284318+00:00",
"title": "Test Content",
"content": "# Hello World\n\nThis is some **MDX content** from a remote source, Supabase."
}
]
Page Component (/examples/remote-mdx):
javascript"use client";
import { createClient } from "@/utils/supabase/client";
import { evaluate } from "@mdx-js/mdx";
import { useEffect, useState } from "react";
import { Fragment } from "react";
import * as runtime from "react/jsx-runtime";
import { MDXProvider } from "@mdx-js/react";
import { useMDXComponents } from "@/mdx-components"; // Adjust path as needed
export default function RemoteMDXPage() {
const [Component, setComponent] = useState<React.ComponentType | null>(null);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<{
id: number;
title: string;
content: string;
} | null>(null);
useEffect(() => {
async function fetchMDXFromSupabase() {
try {
const supabase = createClient();
// Fetch the MDX content column from the `mdx_content` table
{ data, error } = supabase
.()
.()
.(, )
.();
(data);
(error) {
();
}
rawMDX = data?.;
(!rawMDX) {
();
}
{ : } = evaluate(rawMDX, {
...runtime,
,
: ({}),
});
( );
} (err) {
.(, err);
(err ? err. : );
}
}
();
}, []);
(error) {
;
}
(!data) {
;
}
(
);
}
How It Works
- The MDX content is stored in a Supabase table (
mdx_content) with acontentcolumn containing MDX. - The content is fetched via the Supabase client using a query.
- The raw MDX string is compiled into a React component at runtime using the
@mdx-js/mdxevaluatefunction. - The
MDXProviderwraps the rendered content, ensuring custom MDX components (like styledh1,p,code, etc.) are applied. - The dynamically created React component is rendered once ready, styled consistently with locally imported MDX.
Comparison of the Two Approaches
| Feature | Local MDX Import | Remote MDX Fetch |
|---|---|---|
| Data Source | Local .mdx file | Remote database or API |
| Processing Time | Compile during build time | Compile dynamically at runtime |
| Use Case | Static content, frequent reuse | Dynamic or user-specific content |
| Performance | Faster (pre-compiled at build time) | Slower (requires fetching and compiling) |
| Styling | Automatically applies custom styles | Requires for custom styles |
Conclusion
Next.js 13+ (currently 15) with @next/mdx offers robust MDX support for both local and dynamic content. For static pages, local MDX imports are straightforward and highly performant. For dynamic content, fetching and rendering MDX at runtime provides incredible flexibility, enabling dynamic and user-driven experiences.
Choose the approach that best fits your application’s needs!