Contentlayer vs Velite vs next-mdx-remote 2026: Which MDX Content Pipeline Should You Use in Next.js?
# Contentlayer vs Velite vs next-mdx-remote 2026: Which MDX Content Pipeline Should You Use in Next.js?
Almost every content-driven site you build in Next.js hits the same wall. You have a folder of Markdown or MDX — blog posts, docs pages, changelog entries — and you need to turn it into typed data your React components can render, with images that work, frontmatter you can trust, and code blocks that highlight. The three most common answers in 2026 are Contentlayer, Velite, and next-mdx-remote, and they solve the problem from three genuinely different angles.
This guide is the practical version of that decision. Not a feature table you could get from three README files, but the trade-offs that actually decide it: type safety, build speed, how each one handles images, whether your content can come from a CMS instead of the filesystem, App Router and Server Component support, and which one you should build a blog, a docs site, or a marketing site on. If you sell content-driven templates, this is also the pipeline your buyers will grep for the moment they open the repo — so picking the right one is part of building something production-ready.
Writing content that a build pipeline will turn into a website
The one-sentence version
import with hot reload and build-time errors. The original package stalled, so in 2026 you use the community contentlayer2 fork to stay compatible with current Next.js.next-mdx-remote/rsc entry for Server Components. No typegen or asset pipeline, but content that does not have to exist at build time.Now the detail that makes the choice for you.
Contentlayer: content-as-data that started the pattern
Contentlayer popularized an idea that now feels obvious: your content should be typed data, not strings you parse by hand on every page. You define a document type — its fields, which are required, what type each is — and Contentlayer scans your content/ folder, validates every file against that definition, compiles the MDX body, and generates a contentlayer/generated module full of typed objects.
// contentlayer.config.ts
import { defineDocumentType, makeSource } from "contentlayer2/source-files";
export const Post = defineDocumentType(() => ({
name: "Post",
filePathPattern: "posts/**/*.mdx",
contentType: "mdx",
fields: {
title: { type: "string", required: true },
date: { type: "date", required: true },
published: { type: "boolean", default: true },
},
computedFields: {
slug: {
type: "string",
resolve: (post) => post._raw.flattenedPath.replace(/^posts\//, ""),
},
},
}));
export default makeSource({ contentDirPath: "content", documentTypes: [Post] });In a component you get autocomplete and compile-time safety for free:
import { allPosts } from "contentlayer/generated";
export default function BlogIndex() {
const posts = allPosts
.filter((p) => p.published)
.sort((a, b) => +new Date(b.date) - +new Date(a.date));
return posts.map((p) => <PostCard key={p.slug} title={p.title} href={`/blog/${p.slug}`} />);
}The developer experience is excellent — and that is exactly why the pattern won. The problem is maintenance. The original contentlayer package went quiet around 2023 and stopped keeping up with new Next.js and MDX releases, so installing it fresh into a Next.js 15+ project in 2026 lands you in peer-dependency and build errors. The community answer is contentlayer2 (with next-contentlayer2), an actively patched fork that is close to a drop-in replacement — plenty of existing sites migrated by swapping the package names and leaving their config untouched.
Use Contentlayer (via contentlayer2) when: you already run it and don't want to rewrite, or you specifically want its exact API and computedFields ergonomics. Be honest about: you are depending on a community fork of a stalled project, and its build times climb on very large content sets.
Velite: the successor most new projects should start with
Velite takes the content-as-data idea and rebuilds it on foundations that hold up in 2026. Instead of a custom field DSL, you describe each collection with a Zod schema — the same validation library you probably already use for forms and API input — so your frontmatter rules are real, composable TypeScript.
// velite.config.ts
import { defineConfig, defineCollection, s } from "velite";
const posts = defineCollection({
name: "Post",
pattern: "posts/**/*.mdx",
schema: s
.object({
title: s.string().max(99),
date: s.isodate(),
cover: s.image(), // copied + fingerprinted, returns width/height
excerpt: s.string().max(200),
content: s.mdx(), // compiled MDX
})
.transform((data) => ({ ...data, slug: data.title })),
});
export default defineConfig({
collections: { posts },
mdx: { rehypePlugins: [], remarkPlugins: [] },
});Two things set Velite apart from Contentlayer in daily use. First, images and assets are first-class: s.image() copies the file into your output, fingerprints it for caching, and hands you back the path plus real width and height — which means no layout shift and no separate image-import dance. Second, it is framework-agnostic. Velite emits plain typed JSON and .d.ts files into a folder; there is no Next.js webpack plugin wrapping your build, so it works the same in Next.js, Astro, Vite, or a plain Node script, and it never fights your bundler during an upgrade.
import { posts } from "./.velite";
// posts is fully typed: title, date, cover.{src,width,height}, excerpt, contentBecause the output is just data, it drops straight into a Server Component with nothing to mark 'use client'. For a new file-based blog, docs site, or changelog, this is the default I reach for.
Use Velite when: you're starting a new repo-based content site and want type safety, schema validation, and image handling without maintenance risk. Be honest about: it's newer than the Contentlayer name, so there are fewer Stack Overflow answers — though the docs are good and the Zod model is familiar.
Code and content pipelines running side by side
next-mdx-remote: for content that isn't in your repo
The other two assume your content is a folder of files you commit. next-mdx-remote makes no such assumption. You give it an MDX string — from anywhere — and it compiles that string into a React component. The source can be a file you read yourself, a field returned by a headless CMS, or a row in your database.
In the App Router you use the RSC entry point, which compiles and renders entirely on the server:
// app/blog/[slug]/page.tsx
import { compileMDX } from "next-mdx-remote/rsc";
import matter from "gray-matter";
import { Callout } from "@/components/callout";
export default async function Page({ params }: { params: { slug: string } }) {
const raw = await getPostFromCms(params.slug); // string from your CMS/DB
const { content, frontmatter } = await compileMDX<{ title: string }>({
source: raw,
components: { Callout },
options: { parseFrontmatter: true },
});
return (
<article>
<h1>{frontmatter.title}</h1>
{content}
</article>
);
}The superpower is dynamism. Because the compile can run at request time, edited or brand-new content appears without a rebuild — exactly what you want for a CMS-driven blog, docs edited by non-developers, or user-generated content. The trade-off is the mirror image of Velite's strengths: no typegen (you type the frontmatter yourself), no asset pipeline (images come from your CMS or an object-storage CDN), and no build-time validation unless you add it. You are wiring more by hand in exchange for content that doesn't have to live in git.
Use next-mdx-remote when: your content is in a CMS or database, or is otherwise dynamic. Be honest about: for a plain folder of files, it's more manual work than Velite for no benefit — reach for it when the content genuinely lives elsewhere.
Head to head
Type safety. Velite (Zod schemas) and Contentlayer (generated types) both give you fully typed content with build-time errors on bad frontmatter. next-mdx-remote gives you whatever types you write yourself — safe if you validate, unchecked if you don't.
Image and asset handling. Velite wins outright: s.image() copies, fingerprints, and returns dimensions. Contentlayer leaves images to you (or a plugin). next-mdx-remote hands images off to your CMS/CDN entirely.
Content source. Contentlayer and Velite are filesystem-only by design. next-mdx-remote reads from anywhere — its whole reason to exist.
Build vs runtime. Contentlayer and Velite are build-time; content is baked at deploy. next-mdx-remote can compile at build or request time, trading fast builds for dynamic content.
App Router / RSC. All three work. Velite and Contentlayer emit data you render on the server with no client boundary. next-mdx-remote's /rsc entry renders in a Server Component with zero client JS.
Maintenance risk. Velite: actively maintained. Contentlayer: original stalled, use the contentlayer2 fork. next-mdx-remote: maintained and widely used.
Build speed at scale. Roughly even for small sites. Velite tends to be the leaner of the two file-based tools; Contentlayer can drag on very large sets. With next-mdx-remote the cost moves to per-page compile. In every case your remark/rehype plugin chain — not the wrapper — is usually the real bottleneck.
Choosing the pipeline that fits how your content is authored
Which should you build on?
gray-matter for frontmatter and a CDN for images.One deliberate omission: @next/mdx, the official plugin, is great when you want a handful of .mdx files that *are* routes (import components, write pages by hand). It's not a content-as-data layer — there's no collection, no typed array to map over — so it's a different tool for a smaller job, not a fourth option in this race.
Building content-driven templates that sell
If you build Next.js blog or documentation templates to sell, the content pipeline is a feature buyers evaluate, not an implementation detail. A few things that separate a template that gets refunded from one that gets recommended:
contentlayer2 and say why in the README.A content template with a clean, typed, well-labeled pipeline is exactly the kind of touch that turns a browse into a sale — and it sits naturally beside the starters buyers already come to CodeCudos for.
The Bottom Line
All three put rendered MDX on the page — but they come at it from three directions, and matching the direction to *where your content lives* is the whole decision.
Reach for Velite when you're starting a file-based content site, stay on contentlayer2 when you're already invested, and reach for next-mdx-remote when your content is dynamic — and don't hesitate to run Velite and next-mdx-remote side by side when different parts of the site are authored in different places.
Ready to turn what you build into income? List your content-driven template or starter on CodeCudos, see where the pipeline fits the wider picture in our best tech stack for web apps in 2026 guide, pick the framework it renders in, or make sure the whole thing reads as production-ready.
