import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
Update the next.config.js
file at your project's root to configure it to use MDX:
next.config.jsconst withMDX = require('@next/mdx')()
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure `pageExtensions` to include MDX files
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
module.exports = withMDX(nextConfig)
Then, create a new MDX page within the /app
directory:
your-project
├── app
│ └── my-mdx-page
│ └── page.mdx
└── package.json
Now you can use markdown and import React components directly inside your MDX page:
import
{ MyComponent } from 'my-components'
# Welcome to my MDX page!
This is some **bold** and _italics_ text.
This is a list in markdown:
- One
- Two
- Three
Checkout my React component:
<MyComponent />
Navigating to the /my-mdx-page
route should display your rendered MDX.
Remote MDX
If your markdown or MDX files or content lives somewhere else, you can fetch it dynamically on the server. This is useful for content stored in a separate local folder, CMS, database, or anywhere else. A popular community package for this use is next-mdx-remote
.
Good to know: Please proceed with caution. MDX compiles to JavaScript and is executed on the server. You should only fetch MDX content from a trusted source, otherwise this can lead to remote code execution (RCE).
The following example uses next-mdx-remote
:
app/my-mdx-page-remote/page.tsximport { MDXRemote } from 'next-mdx-remote/rsc'
export default async function RemoteMdxPage() {
// MDX text - can be from a local file, database, CMS, fetch, anywhere...
const res = await fetch('https://...')
const markdown = await res.text()
return <MDXRemote source={markdown} />
Navigating to the /my-mdx-page-remote
route should display your rendered MDX.
Layouts
To share a layout across MDX pages, you can use the built-in layouts support with the App Router.
app/my-mdx-page/layout.tsxexport default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return <div style={{ color: 'blue' }}>{children}</div>
Remark and Rehype PluginsYou can optionally provide remark
and rehype
plugins to transform the MDX content.
For example, you can use remark-gfm
to support GitHub Flavored Markdown.
Since the remark
and rehype
ecosystem is ESM only, you'll need to use next.config.mjs
as the configuration file.
next.config.mjsimport remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure `pageExtensions`` to include MDX files
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
const withMDX = createMDX({
// Add markdown plugins here, as desired
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
// Wrap MDX and Next.js config with each other
export default withMDX(nextConfig)
FrontmatterFrontmatter is a YAML like key/value pairing that can be used to store data about a page. @next/mdx
does not support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as:
remark-frontmatter
remark-mdx-frontmatter
gray-matter.
To access page metadata with @next/mdx
, you can export a metadata object from within the .mdx
file:
export const metadata = {
author: 'John Doe',
# My MDX page
Custom ElementsOne of the pleasant aspects of using markdown, is that it maps to native HTML
elements, making writing fast, and intuitive:
This is a list in markdown:
- One
- Two
- Three
The above generates the following HTML
:
<p>This is a list in markdown:</p>
<li>One</li>
<li>Two</li>
<li>Three</li>
When you want to style your own elements for a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to HTML
elements.
To do this, open the mdx-components.tsx
file at the root of your application and add custom elements:
mdx-components.tsx
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'
// This file allows you to provide custom React components
// to be used in MDX files. You can import and use any
// React component you want, including inline styles,
// components from other libraries, and more.
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// Allows customizing built-in components, e.g. to add styling.
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...(props as ImageProps)}
...components,
Deep Dive: How do you transform markdown into HTML?React does not natively understand markdown. The markdown plaintext needs to first be transformed into HTML. This can be accomplished with remark
and rehype
.
remark
is an ecosystem of tools around markdown. rehype
is the same, but for HTML. For example, the following code snippet transforms markdown into HTML:
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
main()
async function main() {
const file = await unified()
.use(remarkParse) // Convert into markdown AST
.use(remarkRehype) // Transform to HTML AST
.use(rehypeSanitize) // Sanitize HTML input
.use(rehypeStringify) // Convert AST into serialized HTML
.process('Hello, Next.js!')
console.log(String(file)) // <p>Hello, Next.js!</p>
The remark
and rehype
ecosystem contains plugins for syntax highlighting, linking headings, generating a table of contents, and more.
When using @next/mdx
as shown above, you do not need to use remark
or rehype
directly, as it is handled for you. We're describing it here for a deeper understanding of what the @next/mdx
package is doing underneath.
Using the Rust-based MDX compiler (Experimental)Next.js supports a new MDX compiler written in Rust. This compiler is still experimental and is not recommended for production use. To use the new compiler, you need to configure next.config.js
when you pass it to withMDX
: