← Back to blog
··14 min read

Contentlayer vs Velite vs next-mdx-remote 2026: Which MDX Content Pipeline Should You Use in Next.js?

MDXMarkdownContentlayerVelitenext-mdx-remoteNext.jsReactContent
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

Writing content that a build pipeline will turn into a website

The one-sentence version

  • Contentlayer — content-as-data, the original. Point it at a folder, describe the shape, get fully typed objects you 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.
  • Velite — content-as-data, the modern successor. Zod schemas validate your frontmatter, it compiles MDX, copies and fingerprints images and assets, and emits typed JSON plus TypeScript types. Framework-agnostic, actively maintained, no webpack plugin to fight. The safe default for a new file-based site.
  • next-mdx-remote — compile MDX from anywhere, on demand. Hand it an MDX string from the filesystem, a CMS, or a database and it renders it, at build time or request time, with a first-class 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.

    ts
    // 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:

    tsx
    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.

    ts
    // 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.

    ts
    import { posts } from "./.velite";
    // posts is fully typed: title, date, cover.{src,width,height}, excerpt, content

    Because 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

    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:

    tsx
    // 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

    Choosing the pipeline that fits how your content is authored

    Which should you build on?

  • A new file-based blog, docs, or marketing site → Velite. Type-safe content-as-data, Zod schemas you already know, real image handling, and no dependency on a fork of an abandoned project. This is the 2026 default.
  • You already run Contentlayer → migrate to contentlayer2. It's close to a package-name swap and keeps you on current Next.js without rewriting your config. Only consider moving to Velite if you're already refactoring.
  • Content in a CMS or database → next-mdx-remote. When authors edit outside the repo and pages must update without a redeploy, its request-time compile is the whole point. Pair it with gray-matter for frontmatter and a CDN for images.
  • A hybrid site → use both. Velite for repo-committed content (docs, changelog) and next-mdx-remote for CMS-driven content is a common, clean split — pick per content type, not per project.
  • 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:

  • Default to Velite for file-based templates. Buyers get type safety and working images out of the box, and you're not shipping a dependency on a stalled project. If you must ship Contentlayer, ship contentlayer2 and say why in the README.
  • Make the content model obvious. One schema file, clearly commented, beats frontmatter conventions a buyer has to reverse-engineer. Validation errors that name the offending file are worth more than a paragraph of docs.
  • Don't hard-code the source. A blog template that assumes a folder is fine; a template marketed as "CMS-ready" should isolate the fetch so a buyer can swap files for Sanity or a database without touching the rendering code.
  • Treat it as production-ready. Fingerprint images, handle a missing-post 404, don't compile the same MDX on every request without caching, and keep remark/rehype plugins lean so builds stay fast as the buyer adds content.
  • 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.

  • Contentlayer (contentlayer2) — content-as-data, the original pattern: typed objects from a folder with great DX, at the cost of depending on a community fork of a stalled project. The choice when you already run it.
  • Velite — content-as-data, done for 2026: Zod schemas, typed output, and real image handling, framework-agnostic and actively maintained. The choice for a new file-based blog, docs, or marketing site.
  • next-mdx-remote — compile MDX from anywhere: render strings from a CMS or database at build or request time, with an RSC entry point, at the cost of typegen and asset handling you wire yourself. The choice when your content isn't in the repo.
  • 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.

    Frequently asked questions

    What is the core difference between Contentlayer, Velite, and next-mdx-remote?▾

    The core difference is where the content lives and when it is turned into something React can render. Contentlayer and Velite are both content-as-data tools: they run at build time over a folder of Markdown/MDX files in your repository, validate the frontmatter against a shape you define, compile the body, and generate typed JavaScript objects plus TypeScript types that you import like any other module — so app/blog/page.tsx just does import { allPosts } from 'contentlayer/generated' (or your Velite output) and gets a fully typed array with autocomplete and build-time errors when a file is malformed. The difference between the two is maturity and scope: Contentlayer defined the pattern but its original repository stalled, so most teams now use the community contentlayer2 fork to keep it working with modern Next.js, while Velite is the actively maintained successor that uses Zod for schemas, handles images and other assets out of the box (copying and fingerprinting them), and is framework-agnostic rather than tied to a Next.js webpack plugin. next-mdx-remote works differently: it does not scan a folder or generate types. You give it an MDX string from any source — a file you read yourself, a field from a headless CMS, a row in your database — and it compiles that string into a React component on the spot, at build time or at request time, including a dedicated next-mdx-remote/rsc entry point for React Server Components. In one line: Contentlayer and Velite turn a folder of files into typed data at build time, and next-mdx-remote turns any MDX string into a component whenever you have it.

    Is Contentlayer dead? Should I still use it in 2026?▾

    Contentlayer is not maintained by its original author, but it is not unusable either. The original contentlayer package went quiet around 2023 and did not keep pace with newer Next.js and MDX releases, which is why you will hit peer-dependency and build issues if you install it fresh in a 2026 Next.js 15+ project. The practical answer is the community fork contentlayer2 (published as contentlayer2 and next-contentlayer2), which is actively patched to work with current Next.js and is close to a drop-in replacement — many existing blogs and docs sites simply switched the package names and kept their config. So if you already run Contentlayer, moving to contentlayer2 is the low-risk path and you do not need to rewrite anything. For a brand-new project, though, most teams now start with Velite instead: it gives you the same content-as-data developer experience with Zod schemas, active maintenance, and built-in asset handling, without betting on a fork of an abandoned project. Use Contentlayer/contentlayer2 when you value its exact API or are already invested; choose Velite when you are starting fresh.

    Which one should I use if my content lives in a CMS or database instead of files?▾

    Use next-mdx-remote. Contentlayer and Velite are built around a folder of files in your repository — they scan the filesystem at build time, so they are a poor fit when your content is authored in a headless CMS (Sanity, Contentful, Storyblok) or stored in a database and can change without a redeploy. next-mdx-remote is designed for exactly that case: you fetch the raw MDX string from wherever it lives and pass it to compileMDX (from next-mdx-remote/rsc) or serialize, and it renders. Because the compile can happen at request time, new or edited content shows up without rebuilding the site, which is what you want for a CMS-driven blog, a docs site edited by non-developers, or user-generated content. You lose the automatic typegen and image pipeline that the file-based tools give you, so you parse frontmatter yourself (usually with gray-matter) and handle images through your CMS or an object-storage CDN — but you gain content that is genuinely dynamic. A common hybrid is Velite for the parts of the site that live in the repo (docs, changelog) and next-mdx-remote for the parts that come from a CMS.

    Do all three work with the Next.js App Router and RSC?▾

    Yes, all three work with the App Router, but they get there differently. Velite and Contentlayer/contentlayer2 are build-time tools that emit plain typed data, so they are inherently compatible with Server Components — you import the generated array in a server component and render it, and because it is just data there is no client boundary to worry about; the MDX body is compiled to a component you render on the server. next-mdx-remote historically used a serialize-plus-client-component model built for the Pages Router, but the modern next-mdx-remote/rsc entry point compiles and renders MDX directly inside a Server Component with no client JavaScript required, which is the recommended approach in the App Router. The one thing to watch across all three is custom components used inside MDX (callouts, code blocks, embeds): if those components use hooks or interactivity they must be client components marked with 'use client' and passed into the MDX renderer, regardless of which pipeline you choose. So the pipeline is not the constraint — the interactivity of your MDX components is.

    Which is fastest to build, and does that matter for a large site?▾

    For small sites the build-time difference is negligible; for large content sets it becomes real. Contentlayer and Velite both process every file at build time, so build duration scales with the number of documents — a few hundred posts is fine, but thousands of MDX files with heavy remark/rehype plugin chains can add noticeable minutes, and Contentlayer in particular has been reported to slow down on very large sets. Velite is generally the faster and leaner of the two content-as-data tools and caches sensibly in watch mode. next-mdx-remote shifts the cost around rather than removing it: if you compile at build time (for static generation) it costs the same order of work as the others per page, but if you compile at request time the per-page compile happens on the server on demand, which keeps builds fast at the price of slower first responses unless you cache the compiled output. The honest rule: for a repo-based site up to a few thousand docs any of the three is fine, and if you are truly at large scale you should measure your own plugin chain, because remark/rehype plugins — not the pipeline wrapper — are usually where the time goes.

    Related guides

    Browse Quality-Scored Code

    Every listing on CodeCudos is analyzed for code quality, security, and documentation. Find production-ready components, templates, and apps — or sell your own code and keep 90%.

    Browse Marketplace →