Using ContentLayer with Next.js
What Contentlayer dying under my blog taught me about content infrastructure
- Contentlayer
- Next.js
- MDX
- Static Site Generation
ContentLayer is effectively unmaintained. In its current state, the project cannot be used with next.js 14 due to dependency conflicts. You can see more info or some alternatives like .
There are that Netlify is working on a new version of ContentLayer, but no release date has been announced yet.
I built this blog on Contentlayer. Then the project stalled, its maintainer moved on, and the Callout above is what I had to prepend to my own enthusiastic draft about it. So instead of paraphrasing its documentation, this is the shorter, more honest post: what Contentlayer actually did for me, what runs this blog today, and what I took away from the experience.
What Contentlayer did well
The pitch was simple: your MDX files become typed, importable data. You describe your content schema in code, and the build turns every file into a validated document you can import { allPosts } from 'contentlayer/generated' anywhere in your app. This blog still works exactly that way. Here is a trimmed excerpt of its real config:
The two ideas that earned their keep:
- Schema as code: every post is validated at build time against field definitions (
categoryanddateare required,draftis a boolean, aseriesis a nested type with a title and an order). A typo in frontmatter fails the build instead of silently rendering wrong. - Computed fields: values derived from the content rather than written by hand. This blog computes each post's
slugfrom its file path,readingTimefrom the raw body, and the table of contents from the headings:
The MDX pipeline (remark and rehype plugins for code titles, link previews, citations, footnotes) hangs off the same config, so the whole content layer lives in a couple of files.
Then it died
Contentlayer's development stopped, and Next.js 14 exposed hard dependency conflicts that were never going to be fixed upstream. For a blog whose entire content pipeline sat on top of it, that was uncomfortable: the thing worked, but every Next.js upgrade from then on was a gamble.
What I use now
This blog runs on , a community fork maintained by Timothy Lin. The migration was almost embarrassingly small: swap contentlayer and next-contentlayer for contentlayer2 and next-contentlayer2 in package.json, and update the import paths (contentlayer2/source-files, as in the excerpt above). The schema, the computed fields, and every page that imports from contentlayer/generated carried over unchanged.
What I learned about picking content infrastructure
- Judge the bus factor, not the demo. Contentlayer was a beautifully designed single-maintainer project. That is a risk profile, and I signed up for it without thinking about it.
- Schema-in-code is what saved me. Because the contract between content and code was explicit and small, moving to a fork was an import rename rather than a rewrite. The more of your pipeline lives in declarative config, the cheaper the exit.
- Abandoned is not the end when the tool is simple enough to fork. If I were starting today I would look at contentlayer2 or , which exists specifically as a successor for this use case. Both prove the idea outlived the original project.
The irony is that Contentlayer dying turned out to be the best endorsement of its design: it was replaceable precisely because it was well factored.