DEV Community

Alex Spinov
Alex Spinov

Posted on

Contentlayer Has a Free API: Type-Safe Content for Next.js Applications

What is Contentlayer?

Contentlayer transforms your content (Markdown, MDX, JSON) into type-safe, validated data that integrates seamlessly with Next.js. Think of it as a content SDK that turns your files into a queryable, type-checked database.

No CMS needed. No API calls. Just files → types → components.

Quick Setup

npm install contentlayer2 next-contentlayer2
Enter fullscreen mode Exit fullscreen mode

Note: The community fork contentlayer2 is the actively maintained version.

Define Your Content Schema

// 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 },
    description: { type: "string", required: true },
    published: { type: "boolean", default: true },
    tags: { type: "list", of: { type: "string" } },
  },
  computedFields: {
    slug: {
      type: "string",
      resolve: (post) => post._raw.flattenedPath.replace("posts/", ""),
    },
    readingTime: {
      type: "number",
      resolve: (post) => Math.ceil(post.body.raw.split(/\s+/).length / 200),
    },
  },
}));

export default makeSource({
  contentDirPath: "content",
  documentTypes: [Post],
});
Enter fullscreen mode Exit fullscreen mode

Use Content in Next.js

// app/blog/page.tsx
import { allPosts } from "contentlayer/generated";
import { compareDesc } from "date-fns";

export default function BlogPage() {
  const posts = allPosts
    .filter((post) => post.published)
    .sort((a, b) => compareDesc(new Date(a.date), new Date(b.date)));

  return (
    <div>
      <h1>Blog</h1>
      {posts.map((post) => (
        <article key={post.slug}>
          <h2>{post.title}</h2>
          <p>{post.description}</p>
          <span>{post.readingTime} min read</span>
          <time>{post.date}</time>
        </article>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Everything is fully typed. post.title is string, post.date is string, post.tags is string[]. TypeScript catches errors at build time.

Dynamic Page Routes

// app/blog/[slug]/page.tsx
import { allPosts } from "contentlayer/generated";
import { useMDXComponent } from "next-contentlayer2/hooks";
import { notFound } from "next/navigation";

export function generateStaticParams() {
  return allPosts.map((post) => ({ slug: post.slug }));
}

export default function PostPage({ params }: { params: { slug: string } }) {
  const post = allPosts.find((p) => p.slug === params.slug);
  if (!post) notFound();

  const MDXContent = useMDXComponent(post.body.code);

  return (
    <article>
      <h1>{post.title}</h1>
      <MDXContent />
    </article>
  );
}
Enter fullscreen mode Exit fullscreen mode

Multiple Content Types

export const Project = defineDocumentType(() => ({
  name: "Project",
  filePathPattern: "projects/**/*.mdx",
  fields: {
    title: { type: "string", required: true },
    github: { type: "string" },
    demo: { type: "string" },
    stack: { type: "list", of: { type: "string" } },
  },
}));

export const Author = defineDocumentType(() => ({
  name: "Author",
  filePathPattern: "authors/**/*.json",
  contentType: "data",
  fields: {
    name: { type: "string", required: true },
    avatar: { type: "string" },
    twitter: { type: "string" },
  },
}));
Enter fullscreen mode Exit fullscreen mode

Each content type gets its own generated module: allProjects, allAuthors.

Build-Time Validation

If a post is missing a required field:

Error: Missing required field "title" in content/posts/my-post.mdx
Enter fullscreen mode Exit fullscreen mode

Content errors are caught at build time, not runtime. No more broken pages from missing frontmatter.

Why Contentlayer?

Feature Contentlayer Manual MDX CMS API
Type Safety ✅ Full ❌ None ⚠️ Manual
Build Validation
Computed Fields Manual Manual
Hot Reload ⚠️
No External API

Building a content-heavy site? I help developers create tools and automation that save hours.

📧 spinov001@gmail.com
🔧 My tools on Apify Store

How do you manage content in your Next.js apps? Let me know below!

Top comments (0)