Replacing Contentlayer with Markdownlayer

Content management is an essential aspect of web development, allowing developers to separate code from content and make it easier for non-technical users to update and manage website content. Contentlayer has been a popular library for this purpose, offering features such as content modeling, automatic API generation, and content querying. However, due to its lack of maintenance and increasing number of bugs, developers are now looking for alternatives.

Having experienced several issues with Contentlayer, I decided to write a similar library to solve the problems I had but with a narrower focus. I named it Markdownlayer. It builds upon the concepts of Contentlayer but addresses the issues and provides a simpler, more efficient solution. In this blog post, we will explore the benefits of migrating from Contentlayer to Markdownlayer and how it can enhance your web development workflow.

Why not just pick and maintain Contentlayer

I reached out to the maintainer and the requirement was to express interest in the GitHub repository. I decided to read through the codebase and even cloned it to get a better understanding. During the process, I discovered the code base was too complex for me to become a maintainer. I abandoned that effort but still needed a solution.

Why did I need a replacement?

Besides having a recently maintained solution, several issues needed to be resolved. I look through each of these.

  1. Automatic detection of markdown vs MDX

    With Contentlayer, each document type has to pick a type which means you cannot have a mix of Markdown and MDX content. Either this is a result of using mdx-bundler or just a design decision. mdx-bundler is a light wrapper around @mdx-js/esbuild and the latter supports both markdown and MDX. With Markdownlayer, you can force the type, but you can also pass detect. I was migrating a documentation site from Docusaurus and this made it necessary due to the mix of files. The majority of the files were in Markdown but a few were MDX. Having to edit all the files to support MDX was too much work, especially for the comments.

  2. Support for Markdoc

    With automatic detection of the document type. It was now possible to add support for Markdoc. I needed this for writing some new documentation pages. If you do not already know about Markdoc, you can read more on it at

  3. Default features

    If you have tried and migrate from one documentation/blogging framework to another, you have found that the default features tend to vary. This was my case migrating from Docusaurus. By default, Markdownlayer includes support for:

    • Remark GFM for GitHub-flavored Markdown support
    • Admonitions for adding informational or warning boxes
    • Reading Time estimation
    • Emoji support
    • Slug generation
    • Table of contents

    With these defaults, one can get started very fast. In time, I am looking at adding more defaults such as code tabs, shiki.

  4. Last update time and author

    In most cases, the content pulled in is also committed to a git repository. With the Docusaurus and shelljs, Markdownlayer surfaces that information for all documents. Currently, this cannot be disabled but if you need to disable it send a shout on GitHub

Other differences between Contentlayer and Markdownlayer are listed here.


I have created a starter sample which is the easiest way to see how to set up Markdownlayer. Check it out at

Before we proceed with migration, it is important to note that Markdownlayer only supports ESM at the moment because the whole Markdown/MDX ecosystem is ESM only. If you need CJS support, reach out on GitHub. Supporting it would likely be similar to how Docusaurus does it (loading modules dynamically instead of globally).

Update your package.json

   "name": "my-app",
++ "type": "module",
   "dependencies": {
--   "contentlayer": "0.3.4",
++   "markdownlayer": "0.2.0",
     "next": "14.1.3",
--   "next-contentlayer": "0.3.4",
     "react": "18.2.0",

Run npm install, yarn, or pnpm install to effect the changes

Update your next.config.js or next.config.mjs

-- const { withContentlayer } = require('next-contentlayer');
++ import { withMarkdownlayer } from 'markdownlayer';
   /** @type {import('next').NextConfig} */
   const nextConfig = {
     // your normal config here
-- module.exports = withContentlayer(nextConfig);
++ export default withMarkdownlayer(nextConfig);

Update your tsconfig.json

    "compilerOptions": {
      "plugins": [{ "name": "next" }],
      "paths": {
        "@/*": ["./src/*"],
--      "contentlayer/generated": ["./.contentlayer/generated"]
++      "markdownlayer/generated": ["./.markdownlayer/generated"]
--  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".contentlayer/generated"],
++  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".markdownlayer/generated"],
    "exclude": ["node_modules"]

Update your imports

-- import { BlogPost, allBlogPosts } from 'contentlayer/generated';
++ import { allBlogPosts, type BlogPost } from 'markdownlayer/generated';

Update your configuration file

Rename contentlayer.config.ts to markdownlayer.config.ts then modify it to look like the one in the starter here.

The content will vary based on your current setup for plugins and document definition.

Overall, the migration should be easy. I tried my best to make sure the fields, properties, and methods are all documented which feels better than creating a whole documentation website.


Overall, this was a great effort in learning the internals for handling markdown, MDX, and Markdoc in the JavaScript/TypeScript ecosystem. By the time I was open-sourcing the repository, I had been running the library as a package in a live system for about 3 months.

There are some things that I ignored when writing this library such as support for anything else besides NextJS. Maybe in the future, I will do so.

If you like this work, feel free to sponsor my work here.