# How to Use next‑mdx‑remote to Render MDX Content in Next.js

> Learn to render MDX content in Next.js using next-mdx-remote. Dynamically load Markdown files and process them with custom components via a plugin pipeline.

- Repository: [Ege Chelebi/blog](https://github.com/woosal1337/blog)
- Tags: how-to-guide
- Published: 2026-08-06

---

**`next‑mdx‑remote` serializes MDX source into React-compatible code at build time, enabling you to load Markdown files dynamically and render them with custom components using a plugin-based processing pipeline.**

The woosal1337/blog repository implements a complete `next‑mdx‑remote` workflow that decouples content from the Next.js build process. This approach allows the blog to store articles as static `.mdx` files while applying custom remark/rehype plugins and Tailwind-styled components for maximum rendering flexibility.

## Why Use next‑mdx‑remote?

Unlike Next.js built-in MDX support, `next‑mdx‑remote` lets you fetch content from external sources or local filesystems without bundling MDX files through the webpack loader. In the woosal1337/blog repository, this pattern enables granular control over the compilation process—specifically through centralized options in `lib/mdx-options.mjs` and a custom component registry in [`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx).

## Configuring MDX Processing Options

The repository centralizes its MDX configuration in **`lib/mdx-options.mjs`**. This file exports an `mdxOptions` object that configures remark and rehype plugins for GitHub-flavored Markdown and syntax highlighting.

```javascript
// lib/mdx-options.mjs
import remarkGfm from 'remark-gfm';
import rehypePrettyCode from 'rehype-pretty-code';

export const mdxOptions = {
  remarkPlugins: [remarkGfm],
  rehypePlugins: [
    [
      rehypePrettyCode,
      {
        theme: { light: 'github-light', dark: 'github-dark' },
        keepBackground: false,
      },
    ],
  ],
};

```

This options object gets passed to the `serialize` function to ensure consistent processing across all MDX content.

## Serializing MDX Content with next‑mdx‑remote

To convert raw MDX strings into renderable React code, the repository uses the **`serialize`** function from `next‑mdx‑remote/serialize`. In **[`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts)**, a helper function reads the `.mdx` file and processes it through the serialization pipeline.

```typescript
// lib/blog.ts
import { readFile } from 'fs/promises';
import { join } from 'path';
import { serialize } from 'next-mdx-remote/serialize';
import { mdxOptions } from './mdx-options.mjs';

export async function getPost(slug: string) {
  const filePath = join(process.cwd(), 'app/(website)/blog', `${slug}.mdx`);
  const mdxString = await readFile(filePath, 'utf-8');

  const source = await serialize(mdxString, { mdxOptions });
  return { source };
}

```

The resulting `source` object contains the compiled component code and metadata required by the `MDXRemote` renderer.

## Defining Custom Components for Rendering

Before rendering, the repository defines a component mapping that aligns with its dark-only design system. The **[`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx)** file exports a `useMDXComponents` hook that merges default elements with custom primitives like styled headings and code blocks.

```typescript
// mdx-components.tsx
export function useMDXComponents(components: any) {
  return {
    h1: (props: any) => <h1 className="text-3xl font-bold text-white" {...props} />,
    pre: (props: any) => <pre className="rounded-lg bg-slate-900 p-4" {...props} />,
    ...components,
  };
}

```

This mapping ensures that standard Markdown elements (headings, lists, code blocks) render with the site's specific Tailwind classes.

## Rendering MDX in Next.js Pages

Finally, the repository renders the serialized content using the **`MDXRemote`** component from `next‑mdx‑remote`. In **`app/(website)/blog/[slug]/page.tsx`**, a server component fetches the serialized data and injects the custom component map.

```tsx
// app/(website)/blog/[slug]/page.tsx
import { getPost } from '@/lib/blog';
import { MDXRemote } from 'next-mdx-remote';
import { useMDXComponents } from '@/mdx-components';

export default async function Page({ params }: { params: { slug: string } }) {
  const { source } = await getPost(params.slug);
  const components = useMDXComponents({});
  
  return (
    <article className="prose prose-dark mx-auto">
      <MDXRemote {...source} components={components} />
    </article>
  );
}

```

Because `next‑mdx‑remote` version `^6.0.0` supports React Server Components, this pattern works seamlessly with the App Router without requiring client-side hydration wrappers.

## Summary

- **`lib/mdx-options.mjs`** centralizes remark and rehype plugin configuration for consistent MDX processing.
- **`serialize`** from `next‑mdx‑remote/serialize` compiles raw MDX strings into renderable objects at build time.
- **[`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx)** provides a `useMDXComponents` hook that maps Markdown elements to Tailwind-styled React components.
- **`MDXRemote`** accepts the serialized source and component map to render JSX-enhanced Markdown in Next.js pages.
- The woosal1337/blog implementation uses `next‑mdx‑remote` version `^6.0.0` with the App Router for optimal static site generation.

## Frequently Asked Questions

### Can next‑mdx‑remote be used with the Next.js App Router?

Yes. Version `^6.0.0` of `next‑mdx‑remote` fully supports React Server Components. You can call `serialize` in server components and render `<MDXRemote>` directly without `"use client"` directives, as demonstrated in the `app/(website)/blog/[slug]/page.tsx` implementation.

### How does serialization differ from traditional MDX loading?

Traditional MDX loaders bundle files at build time through webpack, limiting dynamic content sourcing. The **`serialize`** function processes MDX strings on-demand, enabling you to fetch content from CMSs, databases, or local filesystems while still applying custom plugins and component mappings.

### What plugins work with next‑mdx‑remote?

Any remark or rehype plugin compatible with the unified ecosystem works with `next‑mdx‑remote`. The woosal1337/blog repository uses **`remark-gfm`** for GitHub-flavored Markdown tables and **`rehype-pretty-code`** for Shiki-powered syntax highlighting, configured through the `mdxOptions` object.

### Does MDXRemote require client-side JavaScript?

No. When used with Server Components in Next.js, `MDXRemote` renders static HTML at build time. The component only hydrates if you pass interactive client components in your `components` map. For static content like blog posts, the rendered output requires zero client-side JavaScript.