S
SyedBlog
All ArticlesEngineeringCompanyEducationCustomer Stories
Explore
S
SyedBlog

Engineering insights, high-scale digital architecture, and modern software tutorials by Syed.

Browse all articles

Topics

  • Engineering
  • Architecture
  • Cloud Edge
  • AI & Automation

Categories

  • Company News
  • Education
  • Engineering
  • Customer Stories
  • All Articles

Resources

  • Tutorials & Guides
  • Changelog
  • Tech Stack
  • Documentation
  • Source Code

Company

  • About Syed Blog
  • Authors & Team
  • Careers
  • Contact
  • Privacy Policy
  • Terms of Service

Fresh writing, thoughtfully published

BUILDIN PUBLICMade for builders

© 2026 Syed Blog. All rights reserved.

EngineeringLast updated • July 5, 2024

Migrating from Contentlayer to Content Collections

We recently migrated our content framework from Contentlayer to Content Collections. Here's why and how we did it.

Migrating from Contentlayer to Content Collections

Contentlayer is a popular content SDK that transforms your Markdown & MDX content into type-safe JSON data that you can easily import into your React applications.

However, ever since their main sponsor, Stackbit, was acquired by Netlify, Contentlayer has been effectively unmaintained. Couple that with issues like Next.js App Router incompatibility, and it was clear that a drop-in replacement was needed.

Enter Content Collections.

What is Content Collections?

Content Collections is a Contentlayer-inspired content framework created by Sebastian Sdorra. Frustrated by the lack of flexibility in Contentlayer's schema definition (e.g. you can't define an email or url type for a given field), Sebastian decided to create a drop-in replacement for Contentlayer that addresses these limitations.

Why Content Collections?

Here are several reasons why Content Collections is the best drop-in successor to Contentlayer:

1. Zod-powered Schema Validation

If you're already using Zod for schema validation, Content Collections will feel like second nature to you. To define a collection schema, you can easily use the schema attribute, which comes with a Zod parameter z:

content-collections.ts

code
 import { defineCollection, defineConfig } from "@content-collections/core" ; 

 const BlogPost = defineCollection ({ 
 name: "BlogPost" , 
 directory: "content/blog" , 
 include: "*.mdx" , 
 schema : ( z ) => ({ 
 title: z. string (), 
 categories: z 
 . array (z. enum ([ "company" , "engineering" , "education" , "customers" ])) 
 . default ([ "company" ]), 
 publishedAt: z. string (). datetime (), 
 featured: z. boolean (). default ( false ), 
 }), 
 }); 

 export default defineConfig ({ 
 collections: [BlogPost], 
 }); 
2. Native Fumadocs integration

The creator of Fumadocs – an increasingly popular open-source documentation framework used by projects like Million and Turborepo – recently added native support for Content Collections.

Interestingly, support for Contentlayer has also been deprecated.

3. Compatible with Next.js App Router

Content Collections is compatible with Next.js App Router and React Server Components (RSC).

For example, to render the GitHub repository cards in this blog post, you can create a server component that fetches the latest stats for the repository and renders a React component:

components/github-repo.tsx lib/github.ts

code
 import { getRepo } from "@/lib/github" ; 
 import { Suspense } from "react" ; 

 export function GithubRepo ({ url } : { url : string }) { 
 return ( 
 Suspense fallback = { div >Loading... div >}> 
 GithubRepoRSC url = {url} /> 
 Suspense > 
 ); 
 } 

 async function GithubRepoRSC ({ url } : { url : string }) { 
 const { description , stars , forks } = await getRepo (url); 
 return ( 
 div className = "not-prose grid gap-4" > 
 h3 >{description} h3 > 
 p >{stars} stars p > 
 p >{forks} forks p > 
 div > 
 ); 
 } 

Then, include it in your MDX content:

components/mdx

code
 import { useMDXComponent } from "@content-collections/mdx/react" ; 
 import { GithubRepo } from "./github-repo" ; 

 export function MDX ({ code , className } : { code : string ; className ?: string }) { 
 const Component = useMDXComponent (code); 

 return ( 
 article > 
 Component 
 components = {{ 
 GithubRepo, 
 }} 
 /> 
 article > 
 ); 
 } 
4. Actively maintained by an incredible developer

Content Collections is actively maintained by Sebastian Sdorra. If you've enjoyed using Content Collections, we highly recommend sponsoring Sebastian to help him maintain this project.

How to migrate from Contentlayer to Content Collections?

Since Content Collections is a drop-in replacement for Contentlayer, migrating from Contentlayer to Content Collections is fairly straightforward.

[1

Installation](#installation)

First, install the required Content Collections packages.

If you're using Next.js, you'll also need to install @content-collections/next as well as @content-collections/mdx for MDX support.

npm pnpm yarn bun

code
 pnpm add --save-dev @content-collections/core @content-collections/mdx @content-collections/next 

[2

Update tsconfig.json](#update-tsconfigjson) Then, adjust your tsconfig.json file:

tsconfig.json

code
 { 
 "compilerOptions" : { 
 // ... 
 "paths" : { 
 "@/*" : [ "./*" ], 
 "contentlayer/generated" : [ "./.contentlayer/generated" ], 
 "content-collections" : [ "./.content-collections/generated" ] 
 } 
 } 
 } 

[3

Update your next.config.js](#update-your-nextconfigjs) Next, update your next.config.js file:

next.config.js

code
 // import { withContentlayer } from "next-contentlayer"; 
 import { withContentCollections } from "@content-collections/next" ; 

 // export default withContentlayer(nextConfig); 
 export default withContentCollections (nextConfig); 

[4

Create content-collections.ts file](#create-content-collectionsts-file) Next, add a content-collections.ts file at the root of your project. As you can see, the main difference is the usage of Zod for schema validation.

contentlayer.config.ts content-collections.ts

code
 import { defineCollection, defineConfig } from "@content-collections/core" ; 

 const BlogPost = defineCollection ({ 
 name: "BlogPost" , 
 directory: "content/blog" , 
 include: "*.mdx" , 
 schema : ( z ) => ({ 
 title: z. string (), 
 categories: z 
 . array (z. enum ([ "company" , "engineering" , "education" , "customers" ])) 
 . default ([ "company" ]), 
 }), 
 }); 

 export default defineConfig ({ 
 collections: [BlogPost], 
 }); 

If you're using any Remark or Rehype plugins, you'll need to update them accordingly as well. Since Content Collections does not compile or parse the content by default, you'll need to use the transform option to pass the plugins to the compiler:

contentlayer.config.ts content-collections.ts

code
 import { defineCollection, defineConfig } from "@content-collections/core" ; 
 import { compileMDX } from "@content-collections/mdx" ; 
 import { rehypeCode, remarkGfm } from "fumadocs-core/mdx-plugins" ; 

 async function transformMDX ( document ) { 
 const body = await compileMDX (context, document, { 
 ... options, 
 remarkPlugins: [remarkGfm], 
 rehypePlugins: [[rehypeCode]], 
 }); 
 return { 
 ... document, 
 body, 
 }; 
 } 

 const BlogPost = defineCollection ({ 
 name: "BlogPost" , 
 directory: "content/blog" , 
 include: "*.mdx" , 
 schema : ( z ) => ({ 
 title: z. string (), 
 categories: z 
 . array (z. enum ([ "company" , "engineering" , "education" , "customers" ])) 
 . default ([ "company" ]), 
 }), 
 transform: transformMDX, 
 }); 

 export default defineConfig ({ 
 collections: [BlogPost], 
 }); 

The reason why Content Collections does not parse or compile the content by default is for flexibility - you can essentially bring your own Markdown formatter/library and use it with Content Collections.

[5

Update content imports](#update-content-imports) Finally, update your imports to use the new content-collections source. This can be as simple as doing a CMD+SHIFT+F and replacing contentlayer/generated with content-collections.

blog/page.tsx

code
 // import { allBlogPosts } from "contentlayer/generated"; 
 import { allBlogPosts } from "content-collections" ; 

 export default function Blog () { 
 const posts = allBlogPosts (); 
 return ( 
 div > 
 {posts. map (( post ) => ( 
 BlogPost post = {post} /> 
 ))} 
 div > 
 ); 
 } 

You might also need to update your MDX component's code attribute as well:

blog/[slug]/page.tsx

code
 import { MDX } from "@/components/mdx" ; 

 export default function BlogPost ({ params } : { params : { slug : string } }) { 
 const posts = allBlogPosts (); 
 const post = posts. find (( post ) => post.slug === params.slug); 
 return ( 
 MDX 
 // code={data.body.code} 
 code = {data.body} 
 /> 
 ); 
 } 

[6

Remove Contentlayer dependencies & files](#remove-contentlayer-dependencies--files) Finally, remove any Contentlayer dependencies and files from your project.

npm pnpm yarn bun

code
 pnpm remove contentlayer next-contentlayer 
 rm -rf .contentlayer contentlayer.config.ts 

And voilà! You've successfully migrated your project from Contentlayer to Content Collections.

Scale your content pipeline with Content Collections

We're very excited about what this migration to Content Collections unlocks for Dub. Not only does it give us a much better developer experience with its Zod schema validation, but it also allows us to ensure our content pipeline is more robust and reliable in the long run.

As always, our codebase is fully open-source, so feel free to check it out and learn more about the latest tech we're using at Dub:

And if you're looking for an actively maintained, modern alternative to Contentlayer, we highly recommend giving Content Collections a try.

Steven Tey

Steven Tey

Core Engineering

“Dub is the ultimate partner infrastructure for every startup. If you're looking to 10x your community / product-led growth – I cannot recommend building a partner program with Dub enough.”

Read more

  • Engineering Work Strategy: Building Resilient Edge Architecture at RenderX

    Engineering Work Strategy: Building Resilient Edge Architecture at RenderX

    A deep dive into RenderX's core engineering strategy — from distributed edge compute and sub-50ms data pipelines to multi-cloud fault tolerance and zero-downtime deployments.

    September 15, 2026

  • Announcing the Dub API General Availability (GA)

    Announcing the Dub API General Availability (GA)

    We're excited to announce that the Dub API is now generally available, with native SDKs in the languages you love.

    July 9, 2024

  • Building a Scalable & Affordable Image Hosting Pipeline with Cloudflare R2

    Building a Scalable & Affordable Image Hosting Pipeline with Cloudflare R2

    We recently migrated our image hosting from Cloudinary to Cloudflare R2. This post details the reasons behind the switch, the pros and cons of Cloudflare R2, and how we implemented it in our codebase.

    March 26, 2024

  • Designing the Future of Work: How Modern Workspaces Drive Culture, Innovation, and Growth

    Designing the Future of Work: How Modern Workspaces Drive Culture, Innovation, and Growth

    A practical look at how modern workspaces combine flexibility, technology, well-being, and culture to create environments where people and businesses can thrive.

    September 19, 2026

Written by

Steven Tey

Steven Tey

Core Engineering

On this page

What is Content Collections?Why Content Collections?How to migrate from Contentlayer to Content Collections?Installation](#installation)Update tsconfig.json](#update-tsconfigjson) Then, adjust your tsconfig.json file:Update your next.config.js](#update-your-nextconfigjs) Next, update your next.config.js file:Create content-collections.ts file](#create-content-collectionsts-file) Next, add a content-collections.ts file at the root of your project. As you can see, the main difference is the usage of Zod for schema validation.Update content imports](#update-content-imports) Finally, update your imports to use the new content-collections source. This can be as simple as doing a CMD+SHIFT+F and replacing contentlayer/generated with content-collections.Remove Contentlayer dependencies & files](#remove-contentlayer-dependencies--files) Finally, remove any Contentlayer dependencies and files from your project.Scale your content pipeline with Content Collections
Syed Blog

Explore Syed Blog

Empower your knowledge with high-scale architecture, engineering deep dives, and modern web insights.

Supercharge your digital knowledge

See why Syed Blog is the destination of choice for forward-thinking software engineers and tech leaders.

Read ArticlesEngineering Insights
G2
P
★