
Next.js mdx-components.js
If you've configured @next/mdx in the App Router and your MDX files are rendering with completely unstyled HTML — plain black links, default browser heading sizes, no code syntax highlighting — the odds are good you're missing this one file. mdx-components.js isn't an optional nicety; it's a required file for @next/mdx to function in the App Router at all, and understanding exactly why it's required (rather than just copy-pasting the boilerplate) makes the rest of your MDX styling setup much less mysterious.
This is a short, focused reference — the file itself exports exactly one function with exactly one job.
Why This File Is Required
MDX files blend Markdown syntax with JSX, which means every Markdown construct — a # Heading, a [link](url), a fenced code block — ultimately needs to render as some React component. Left entirely to its defaults, MDX would render these as bare HTML elements with zero Next.js-specific behavior: a Markdown link becomes a plain <a> tag rather than the App Router's optimized next/link, for instance.
mdx-components.js is the mechanism that lets you intercept that mapping — telling the MDX renderer "when you encounter an h1, use this component instead of a raw <h1>; when you encounter an a, use this one instead of a raw anchor." Because the App Router needs this mapping to exist structurally (there's no other place in the convention system where it could live), the file's presence isn't optional configuration — it's part of the contract @next/mdx requires to even start rendering MDX content in the App Router.
File Location
Place mdx-components.tsx (or .js) in the root of your project — the same level as your app directory (or pages, if you're still on that router), or inside src if your project uses that layout convention. It is explicitly not a route-level file; it doesn't live inside app alongside your pages and layouts.
The Required Export: useMDXComponents
The file must export exactly one function, named exactly useMDXComponents, which takes no arguments:
import type { MDXComponents } from "mdx/types";
const components: MDXComponents = {};
export function useMDXComponents(): MDXComponents {
return components;
}
An empty components object, as shown here, is a completely valid — if minimal — starting point. It satisfies the required contract (the function exists, it returns an object typed as MDXComponents) without customizing anything yet. Every project using MDX in the App Router needs at minimum this shell; the actual styling work happens by populating that object.
Customizing Styles and Components
The real value of this file comes from filling in that components object with actual overrides, mapping standard HTML element names to your own React components:
import type { MDXComponents } from "mdx/types";
import Image, { ImageProps } from "next/image";
export function useMDXComponents(): MDXComponents {
return {
h1: ({ children }) => (
<h1 style={{ fontSize: "2.5rem", fontWeight: 700 }}>{children}</h1>
),
a: ({ href, children }) => (
<a href={href} style={{ color: "#0366d6" }}>
{children}
</a>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: "100%", height: "auto" }}
{...(props as ImageProps)}
/>
),
};
}
This is where the practical value shows up. Swapping the default img tag mapping for Next.js's Image component, for instance, means every image referenced from Markdown syntax across your entire MDX content automatically gets the App Router's built-in image optimization — lazy loading, responsive sizing, format negotiation — without you having to hand-author <Image> components inside every individual .mdx file. The override happens once, centrally, and applies everywhere MDX content renders.
The same logic extends to anything else you'd want consistently themed across MDX content: code blocks routed through a syntax-highlighting component, blockquotes styled to match your design system, tables given consistent spacing — all of it flows through this one function rather than being repeated per-file.
Version History
| Version | Changes |
|---|---|
v13.1.2 | MDX Components support added |
Key Takeaways
| Aspect | Detail |
|---|---|
| Required? | Yes — @next/mdx will not work in the App Router without this file |
| Location | Project root (or src), same level as app — not inside a route |
| Required export | useMDXComponents, no arguments, returning an MDXComponents object |
| Minimum valid setup | An empty components object still satisfies the contract |
| Practical use | Override default HTML-element renders (img, a, h1, etc.) with your own styled or optimized components |
Once this file exists and exports a valid useMDXComponents function, the rest of your MDX styling work is just populating an object — no separate configuration system, no per-file overrides. It's a small file that does exactly one job, and every visual customization you'll ever want to apply consistently across MDX content in your App Router project routes through it.


