--- name: mdx description: Creates content with MDX including Markdown with JSX, custom components, and frontmatter. Use when building content-heavy sites, documentation, blogs, or mixing Markdown with interactive components. --- # MDX Markdown for the component era - write JSX in Markdown documents. ## Quick Start **Install (Next.js):** ```bash npm install @next/mdx @mdx-js/loader @mdx-js/react ``` **Configure next.config.mjs:** ```javascript import createMDX from '@next/mdx'; const withMDX = createMDX({ extension: /\.mdx?$/, options: { remarkPlugins: [], rehypePlugins: [], }, }); export default withMDX({ pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'], }); ``` ## Basic Usage ### MDX File ```mdx --- title: Hello World date: 2024-01-01 --- # {frontmatter.title} This is **Markdown** with JSX. import { Button } from '@/components/ui/button' ## Features - Write Markdown naturally - Import and use React components - Export data and components ``` ### Use in Pages ```tsx // app/blog/[slug]/page.tsx import { notFound } from 'next/navigation'; async function getPost(slug: string) { try { const post = await import(`@/content/blog/${slug}.mdx`); return post; } catch { return null; } } export default async function BlogPost({ params, }: { params: { slug: string }; }) { const post = await getPost(params.slug); if (!post) { notFound(); } const { default: Content, frontmatter } = post; return (

{frontmatter.title}

); } ``` ## Frontmatter ### With gray-matter ```bash npm install gray-matter ``` ```typescript // lib/mdx.ts import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; const postsDirectory = path.join(process.cwd(), 'content/blog'); export function getPostBySlug(slug: string) { const fullPath = path.join(postsDirectory, `${slug}.mdx`); const fileContents = fs.readFileSync(fullPath, 'utf8'); const { data, content } = matter(fileContents); return { slug, frontmatter: data, content, }; } export function getAllPosts() { const slugs = fs.readdirSync(postsDirectory); return slugs .map((slug) => getPostBySlug(slug.replace(/\.mdx$/, ''))) .sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime() ); } ``` ### Type-safe Frontmatter ```typescript // lib/mdx.ts import { z } from 'zod'; const frontmatterSchema = z.object({ title: z.string(), date: z.string().transform((s) => new Date(s)), description: z.string().optional(), tags: z.array(z.string()).default([]), published: z.boolean().default(true), }); export type Frontmatter = z.infer; export function getPostBySlug(slug: string) { const { data, content } = matter(fileContents); const frontmatter = frontmatterSchema.parse(data); return { slug, frontmatter, content }; } ``` ## Custom Components ### MDX Components Provider ```tsx // components/mdx-components.tsx import Image from 'next/image'; import Link from 'next/link'; import { Callout } from '@/components/callout'; import { CodeBlock } from '@/components/code-block'; export const mdxComponents = { // Override HTML elements h1: (props: any) => (

), h2: (props: any) => (

), p: (props: any) => (

), a: (props: any) => ( ), img: (props: any) => ( ), pre: (props: any) => , code: (props: any) => ( ), // Custom components Callout, Image, }; ``` ### Using Provider ```tsx // app/layout.tsx import { MDXProvider } from '@mdx-js/react'; import { mdxComponents } from '@/components/mdx-components'; export default function Layout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### App Router (Next.js 14+) ```tsx // mdx-components.tsx (root level) import type { MDXComponents } from 'mdx/types'; import { mdxComponents } from '@/components/mdx-components'; export function useMDXComponents(components: MDXComponents): MDXComponents { return { ...components, ...mdxComponents, }; } ``` ## Plugins ### Remark Plugins (Markdown) ```bash npm install remark-gfm remark-math ``` ```javascript // next.config.mjs import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; const withMDX = createMDX({ options: { remarkPlugins: [remarkGfm, remarkMath], }, }); ``` ### Rehype Plugins (HTML) ```bash npm install rehype-slug rehype-autolink-headings rehype-pretty-code ``` ```javascript // next.config.mjs import rehypeSlug from 'rehype-slug'; import rehypeAutolinkHeadings from 'rehype-autolink-headings'; import rehypePrettyCode from 'rehype-pretty-code'; const withMDX = createMDX({ options: { rehypePlugins: [ rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }], [rehypePrettyCode, { theme: 'github-dark' }], ], }, }); ``` ## Contentlayer (Recommended) ### Setup ```bash npm install contentlayer next-contentlayer ``` ```typescript // contentlayer.config.ts import { defineDocumentType, makeSource } from 'contentlayer/source-files'; import rehypePrettyCode from 'rehype-pretty-code'; import rehypeSlug from 'rehype-slug'; import remarkGfm from 'remark-gfm'; export const Post = defineDocumentType(() => ({ name: 'Post', filePathPattern: 'blog/**/*.mdx', contentType: 'mdx', fields: { title: { type: 'string', required: true }, date: { type: 'date', required: true }, description: { type: 'string' }, published: { type: 'boolean', default: true }, tags: { type: 'list', of: { type: 'string' }, default: [] }, }, computedFields: { slug: { type: 'string', resolve: (post) => post._raw.sourceFileName.replace(/\.mdx$/, ''), }, url: { type: 'string', resolve: (post) => `/blog/${post._raw.sourceFileName.replace(/\.mdx$/, '')}`, }, }, })); export default makeSource({ contentDirPath: 'content', documentTypes: [Post], mdx: { remarkPlugins: [remarkGfm], rehypePlugins: [ rehypeSlug, [rehypePrettyCode, { theme: 'github-dark' }], ], }, }); ``` ### Next.js Config ```javascript // next.config.mjs import { withContentlayer } from 'next-contentlayer'; export default withContentlayer({ // Next.js config }); ``` ### Usage ```tsx // app/blog/[slug]/page.tsx import { allPosts } from 'contentlayer/generated'; import { useMDXComponent } from 'next-contentlayer/hooks'; import { notFound } from 'next/navigation'; import { mdxComponents } from '@/components/mdx-components'; export async function generateStaticParams() { return allPosts.map((post) => ({ slug: post.slug, })); } export default function PostPage({ params }: { params: { slug: string } }) { const post = allPosts.find((post) => post.slug === params.slug); if (!post) { notFound(); } const MDXContent = useMDXComponent(post.body.code); return (

{post.title}

); } ``` ## Remote MDX ### MDX Remote ```bash npm install next-mdx-remote ``` ```tsx // app/blog/[slug]/page.tsx import { MDXRemote } from 'next-mdx-remote/rsc'; import { mdxComponents } from '@/components/mdx-components'; async function getPost(slug: string) { const res = await fetch(`https://api.example.com/posts/${slug}`); return res.json(); } export default async function PostPage({ params, }: { params: { slug: string }; }) { const post = await getPost(params.slug); return (

{post.title}

); } ``` ## Interactive Components ### Code Playground ```mdx import { Playground } from '@/components/playground' setCount(c => c + 1)}> Count: {count} ); } `} /> ``` ### Tabs ```mdx import { Tabs, Tab } from '@/components/tabs' ```bash npm install package-name ``` ```bash yarn add package-name ``` ```bash pnpm add package-name ``` ``` ### Callouts ```tsx // components/callout.tsx interface CalloutProps { type?: 'info' | 'warning' | 'error'; title?: string; children: React.ReactNode; } export function Callout({ type = 'info', title, children }: CalloutProps) { const styles = { info: 'bg-blue-50 border-blue-500', warning: 'bg-yellow-50 border-yellow-500', error: 'bg-red-50 border-red-500', }; return (
{title &&

{title}

} {children}
); } ``` ```mdx This is an important warning message. ``` ## Best Practices 1. **Use type-safe frontmatter** - Validate with Zod 2. **Centralize components** - Single MDX components file 3. **Add table of contents** - Parse headings 4. **Implement syntax highlighting** - Use rehype-pretty-code 5. **Cache compiled MDX** - Contentlayer handles this ## Common Mistakes | Mistake | Fix | |---------|-----| | Missing imports in MDX | Use components provider | | Slow builds | Use Contentlayer | | No syntax highlighting | Add rehype-pretty-code | | Broken links | Use next/link component | | Large bundle | Lazy load components | ## Reference Files - [references/plugins.md](references/plugins.md) - Remark/Rehype plugins - [references/components.md](references/components.md) - Custom components - [references/contentlayer.md](references/contentlayer.md) - Contentlayer setup