MDX Components are Cool (and how to use them)

2025-11-17 (6 days ago)

so you want to write content for your next.js site but you're tired of writing plain html or dealing with complex cms setups. enter mdx - markdown on steroids.

what is mdx anyway?

mdx lets you write jsx directly in your markdown files. sounds simple, and it is. but it's surprisingly powerful.

you write this:

# hello world this is regular markdown. <CustomComponent name="henri" /> and you're back to markdown.

and it just works. react components living alongside your content.

setting it up in next.js 16

first, install the dependencies:

npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

update your next.config.ts:

import createMDX from '@next/mdx' const nextConfig = { pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], } const withMDX = createMDX({ // add markdown plugins here }) export default withMDX(nextConfig)

create mdx-components.tsx at your project root:

import type { MDXComponents } from 'mdx/types' export function useMDXComponents(): MDXComponents { return { // customize your components here h1: ({ children }) => ( <h1 className="text-4xl font-bold">{children}</h1> ), } }

that's it. you're ready.

why i like it

it's just markdown. you don't need to learn a new syntax or deal with visual editors that break. write markdown like you always have.

components when you need them. most of the time, markdown is enough. but when you need something interactive or custom, just drop in a component.

type safety. since it's jsx, you get full typescript support. your ide knows what props your components expect.

no build headaches. next.js handles everything. no webpack configs, no babel plugins to debug.

practical example

here's how you might use it for a blog post:

# my awesome post regular content here. <Callout type="warning"> this is important information </Callout> more content. <CodeBlock language="typescript"> const hello = "world" </CodeBlock> and you're done.

your components get the full power of react - state, effects, whatever you need.

gotchas

mdx-components.tsx is required. even if it's empty. next.js won't work without it.

file-based routing works. create app/blog/my-post/page.mdx and it becomes a route. nice.

dynamic imports. you can load mdx files dynamically based on url params. useful for blog systems.

const { default: Post } = await import(`@/posts/${slug}.mdx`) return <Post />

custom components globally

in mdx-components.tsx, map html elements to your components:

export function useMDXComponents(): MDXComponents { return { h1: ({ children }) => <h1 className="custom">{children}</h1>, img: (props) => <Image {...props} />, a: ({ href, children }) => <Link href={href}>{children}</Link>, } }

now every heading, image, and link in your mdx files uses your custom components.

the tldr

mdx gives you markdown's simplicity with react's power. perfect for content that's mostly text but occasionally needs something interactive.

no cms complexity. no weird templating languages. just markdown and react.

and in next.js 16, it works out of the box with app router, server components, and everything else.

honestly, it's kind of perfect for blogs and documentation sites.