Next.js

Next.js

Integrate MDX UI with a Next.js project using @next/mdx or next-mdx-remote

Overview

Next.js has two common ways to render MDX — pick the one that fits your project:

@next/mdxnext-mdx-remote
MDX files as pagesYesNo
Content from files / CMSWith extra setupYes
FrontmatterNeeds extra pluginBuilt-in
SetupMinimalMinimal

Use @next/mdx when your .mdx files live in app/ and act as pages directly.

Use next-mdx-remote when you load MDX from a content/ folder, a CMS, or a database.


1. Create a Next.js Project

Skip this step if you already have a project.

 

2. Initialize MDX UI

bash pnpm dlx @ravikumarsurya/mdx-ui init

This creates mdx-ui.json and copies the cn() utility into your project.


3. Add Components

bash pnpm dlx @ravikumarsurya/mdx-ui add callout accordion code-block

Auto-updated for you

The CLI copies the component file and patches components/mdx-ui/mdx-components.tsx with the correct imports and mappings automatically.


4. Configure MDX

Install next-mdx-remote

bash pnpm add next-mdx-remote

No changes needed to next.config.tsnext-mdx-remote runs at request time, not at the bundler level.


5. Render MDX Content

Create your MDX files in a content/ folder:

content/blog/hello-world.mdx
---
title: Hello World
date: 2024-01-01
---
 
# Hello World
 
<Callout variant="info" title="Welcome">
  This page is rendered from a `.mdx` file in `content/`.
</Callout>

Create app/blog/[slug]/page.tsx:

app/blog/[slug]/page.tsx
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { MDXRemote } from "next-mdx-remote/rsc";
import { mdxComponents } from "@/components/mdx-ui/mdx-components";
 
export async function generateStaticParams() {
  const files = fs.readdirSync(path.join(process.cwd(), "content/blog"));
  return files.map((f) => ({ slug: f.replace(/\.mdx?$/, "") }));
}
 
async function getPost(slug: string) {
  const filePath = path.join(process.cwd(), "content/blog", `${slug}.mdx`);
  const raw = fs.readFileSync(filePath, "utf8");
  const { content, data } = matter(raw);
  return { content, frontmatter: data };
}
 
export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const { content, frontmatter } = await getPost(params.slug);
 
  return (
    <article className="max-w-3xl mx-auto px-6 py-10">
      <h1 className="text-3xl font-bold mb-2">{frontmatter.title}</h1>
      <MDXRemote source={content} components={mdxComponents} />
    </article>
  );
}

Install gray-matter for frontmatter parsing:

bash pnpm add gray-matter

Project Structure

📂my-app
├── 📂app
├── 📁blog
├── 📄layout.tsx
├── 📂content
├── 📁blog
├── 📂components
├── 📁mdx-ui
├── 📄next.config.ts
├── 📄mdx-ui.json
├── 📄package.json

Math Components

Math uses pure JSX primitives — no KaTeX or LaTeX required:

 

See Math Primitives for the full list.


Troubleshooting

Components not rendering in MDX

Make sure mdxComponents is passed to your renderer:

  • next-mdx-remote: <MDXRemote source={content} components={mdxComponents} />
  • @next/mdx: The root mdx-components.tsx with useMDXComponents wires this up automatically.

Cannot find module @/components/mdx-ui/...

Add the @/ path alias to tsconfig.json:

tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"]
    }
  }
}

TypeScript error on .mdx imports (@next/mdx only)

Add to next-env.d.ts or a .d.ts file:

declare module "*.mdx" {
  import type { ComponentType } from "react";
  const Component: ComponentType;
  export default Component;
}

Tailwind classes not applying

Make sure your globals.css (Tailwind v4) or tailwind.config.ts (Tailwind v3) includes the component paths:

tailwind.config.ts (v3 only)
export default {
  content: [
    "./app/**/*.{ts,tsx,mdx}",
    "./components/**/*.{ts,tsx}",
    "./content/**/*.mdx",
  ],
};

Next Steps