MDX Tooling for Next.js Content Sites in 2026 (next-mdx-remote is Archived)

next-mdx-remote was archived in April 2026 and Contentlayer is unmaintained. Here is what actually works for a Next.js 16 content site now — core MDX on RSC, next-mdx-remote-client, mdx-bundler, contentlayer2, and Velite, compared on DX, build time, and lock-in.

Two of the libraries this comparison used to recommend are now dead ends. Contentlayer lost its maintainer in mid-2023 and never shipped React 19 support. And as of April 9, 2026, next-mdx-remote itself is archived and read-only — its own README now points you elsewhere. So the real 2026 question is not “MDX bundler or next-mdx-remote.” It is: on Next.js 16, with content files in your repo, which MDX path is alive, fast, and gives you back the type-safety DX people loved about Contentlayer?

This article compares five live options on the things that decide a content site: maintenance risk, build time, frontmatter type safety, and bundle size.

TL;DR

  • For a new file-system content site on Next.js 16, you usually need no MDX wrapper at all. The core MDX library (@mdx-js/mdx) compiles fine inside a React Server Component. That is what the archived next-mdx-remote README now recommends.
  • If you want a thin wrapper with error handling and frontmatter helpers, use next-mdx-remote-client (v2.x, actively maintained, React 19, RSC support) — it is the direct successor to the archived package.
  • Want the Contentlayer “generate types from frontmatter” DX back as a managed dependency? Use Velite (build-time, Zod, framework-agnostic). Caveat: its webpack plugin does not run under Turbopack.
  • Use mdx-bundler only when MDX comes from a CMS/database and each post imports different components. Its output is roughly 4x larger than next-mdx-remote for plain content, so it is overkill for repo files.
  • Only consider contentlayer2 (the maintained timlrx fork) if you are migrating an existing Contentlayer app and want a near drop-in upgrade.

The options at a glance (as of June 2026)

ToolStatusBest forFrontmatter typesNotable cost
Core @mdx-js/mdx on RSCActiveNew file-system sitesBring your own ZodYou write ~80 lines
next-mdx-remote-client v2.1.11Active (May 2026)Thin wrapper + error handlingBring your own ZodOne more dependency
VeliteActiveWant Contentlayer DX backGenerated from ZodWebpack plugin breaks on Turbopack
mdx-bundlerActiveMDX from CMS/DBAuto-parsedOutput ~4x larger
contentlayer2 / next-contentlayer2Community forkMigrating old Contentlayer appsGeneratedSmaller community, fork risk
next-mdx-remoteArchived Apr 9 2026Nothing newNo more fixes

Versions and dates above are current as of June 2026; check npm before you commit.

Why the old defaults broke

Both fan favorites died the same way: a single maintainer, then no maintainer.

  • Contentlayer: last meaningful release was mid-2023, before Next.js 14. It generated TypeScript types from your frontmatter and cached parsed MDX across builds — genuinely nice DX — but it never handled the App Router / React 19 era. New installs hit peer-dependency and build failures.
  • next-mdx-remote: HashiCorp archived the repo on April 9, 2026. The last release was v6.0.0 (February 2026). The code still runs, but there will be no fixes, and the README explicitly suggests next-mdx-remote-client, mdx-bundler, or just the core MDX library on RSC.

The lesson for a content site you intend to keep for years: prefer the option with the least dependency surface, so a single archived repo cannot strand you again.

Option 1: core MDX on a Server Component (the new default)

The job is small. An App Router server component reads the file, runs the official MDX compiler, and renders the result. No third-party wrapper.

// app/articles/[slug]/page.tsx
import { compile, run } from '@mdx-js/mdx';
import * as runtime from 'react/jsx-runtime';
import matter from 'gray-matter';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { z } from 'zod';
import { mdxComponents } from '@/components/mdx';

const FrontmatterSchema = z.object({
  title: z.string(),
  description: z.string(),
  publishedAt: z.coerce.date(),
  tags: z.array(z.string()),
});

export default async function ArticlePage(
  { params }: { params: Promise<{ slug: string }> },
) {
  const { slug } = await params;
  const raw = await readFile(
    path.join(process.cwd(), 'content', `${slug}.mdx`),
    'utf8',
  );
  const { content, data } = matter(raw);
  const meta = FrontmatterSchema.parse(data);

  const compiled = await compile(content, { outputFormat: 'function-body' });
  const { default: MDXContent } = await run(compiled, runtime);

  return (
    <article>
      <h1>{meta.title}</h1>
      <time>{meta.publishedAt.toISOString()}</time>
      <MDXContent components={mdxComponents} />
    </article>
  );
}

generateStaticParams reads the content directory; generateMetadata reuses the same Zod schema. The whole pipeline is roughly 80 lines you own outright, with zero archived dependencies. Note that in Next.js 15 and 16, params is a Promise — await it.

Option 2: next-mdx-remote-client (the thin wrapper)

If you would rather not hand-wire compile/run, the maintained successor to next-mdx-remote is next-mdx-remote-client (v2.1.11 as of late May 2026; v2.x targets React 19). It wraps @mdx-js/mdx, supports MDX v3, isolates its server (rsc) and client (csr) exports, and adds an internal error-handling layer plus a “read frontmatter without compiling” helper.

// app/articles/[slug]/page.tsx
import { MDXRemote } from 'next-mdx-remote-client/rsc';
import { mdxComponents } from '@/components/mdx';

export default async function ArticlePage(/* ...read raw, validate... */) {
  return (
    <MDXRemote
      source={raw}
      components={mdxComponents}
      options={{ parseFrontmatter: true }}
    />
  );
}

MDXRemote is an async server component, so it must render on the server. For most file-system sites this is the same job as Option 1 with a friendlier API and built-in error states.

Option 3: mdx-bundler when content lives outside the repo

mdx-bundler (by Kent C. Dodds) compiles MDX into a self-contained JavaScript string you evaluate at runtime. The win appears when MDX ships from a database or headless CMS and each post imports a different set of components — bundler isolates every compile.

// when MDX content comes from a remote source
import { bundleMDX } from 'mdx-bundler';

const { code, frontmatter } = await bundleMDX({
  source: mdxStringFromCMS,
  cwd: process.cwd(),
});

The cost: for plain content, mdx-bundler’s output is around 4x larger than next-mdx-remote’s, because it ships an esbuild-built bundle per post. For repo-based MDX that isolation buys you nothing, so reach for Option 1 or 2 instead.

Replacing Contentlayer’s DX (Velite, or roll your own)

People missed three Contentlayer features. Each has a clean 2026 answer.

  1. Frontmatter type generation. Roll your own with z.infer<typeof FrontmatterSchema> — a 10-line helper. Or get it generated for you by Velite, which turns your content/ folder into a typed data layer (JSON + .d.ts) from Zod schemas, framework-agnostic. Velite builds plain content in milliseconds, but its VeliteWebpackPlugin does not work when Turbopack is enabled — run it as a separate velite step in that case.
  2. Computed fields. Contentlayer derived urlSlug from the filename. Same pattern: a function in lib/content.ts that maps raw frontmatter to a richer object. Velite supports computed fields in the schema directly.
  3. Cached parse across pages. Contentlayer cached parsed MDX between page builds. Replace with a small in-memory cache keyed by file mtime, or skip it — Next.js 16’s caching (including Cache Components) covers most sites.

A minimal hand-rolled lib/content.ts lands around 150-200 lines and gives the same DX without a dead dependency. Velite gets you there with less code at the price of one managed (but active) dependency.

Common mistakes

  • Importing a client-only MDX component into a server compile and getting a hydration mismatch. Keep interactive components in 'use client' files and pass them through the components map.
  • Forgetting generateStaticParams. Every article becomes a dynamic route, killing static SEO and Lighthouse scores. On Next.js 16 this also forfeits prerendering.
  • Running MDX compilation inside a client component. It defeats the point of RSC and ships a large runtime bundle.
  • Validating frontmatter only at runtime. A schema error then crashes production. Run the same Zod parse at build time, ideally in CI.
  • Forgetting that params is now a Promise. In Next.js 15+ you must await params (and searchParams) — code copied from older tutorials will throw.
  • Leaving stale dead dependencies installed. After migrating, run npm uninstall contentlayer next-contentlayer next-mdx-remote to clear peer-dep warnings and shrink your lockfile.

FAQ

  • Is Contentlayer dead?: The original repo has been unmaintained since 2023 and never supported React 19. A community fork, contentlayer2 / next-contentlayer2 (by timlrx), is API-compatible and works with Next.js 15 and React 19. Only adopt it for migrating an existing Contentlayer app, and check repo activity first.
  • Wait, next-mdx-remote is gone?: HashiCorp archived it on April 9, 2026. v6.0.0 still installs and runs, but there are no more fixes. For new work use next-mdx-remote-client or compile with the core MDX library directly.
  • next-mdx-remote vs next-mdx-remote-client — what is the difference?: next-mdx-remote-client (by ipikuka) is the maintained fork. It supports MDX v3, React 19, fully separated rsc/csr entry points, built-in error handling, and a frontmatter-only read helper.
  • Can I just use the official @next/mdx?: Yes when each MDX file is a literal route page (app/articles/foo/page.mdx). It breaks down once you need dynamic routing from a content directory, which is most content sites.
  • Do these support remark / rehype plugins?: Yes. Pass remarkPlugins and rehypePlugins in the options — the core MDX, next-mdx-remote-client, mdx-bundler, and Velite all forward them.
  • What about Astro?: Astro has first-class MDX with content collections and Zod schemas built in — that is what this article’s host site uses. If you want the Contentlayer DX without the Contentlayer problem and you do not need a React-app shell, Astro is the lower-maintenance path.

Tags: #Indie dev #Next.js #MDX #Content #tooling