TechJanuary 2025
使用 Contentlayer 构建现代化博客
一篇偏系统设计的长文,聚焦如何让内容管线、前端渲染和博客维护流程在同一个工程里保持一致。
2025-01-25作者:Scofield更新于 2026-04-13
ContentlayerNext.jsBlog内容管理

什么是 Contentlayer?
Contentlayer 是一个内容 SDK,它可以将 Markdown 和 MDX 文件转换为类型安全的 TypeScript 数据。它完美集成了 Next.js,让你可以像处理普通数据一样处理内容。
为什么选择 Contentlayer?
优势
- 类型安全:自动生成 TypeScript 类型
- 零运行时开销:构建时处理,运行时无额外成本
- MDX 支持:在 Markdown 中使用 React 组件
- 开发体验:热重载、类型提示、自动补全
- 灵活性:支持自定义字段和计算字段
与其他方案对比
| 特性 | Contentlayer | MDX | Markdown Files |
|---|---|---|---|
| 类型安全 | ✅ | ❌ | ❌ |
| 零运行时 | ✅ | ❌ | ✅ |
| MDX 支持 | ✅ | ✅ | ❌ |
| 开发体验 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
安装和配置
1. 安装依赖
npm install contentlayer2 next-contentlayer22. 配置 Contentlayer
创建 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" },
tags: { type: "list", of: { type: "string" } },
},
computedFields: {
slug: {
type: "string",
resolve: (post) => post._raw.flattenedPath,
},
},
}));
export default makeSource({
contentDirPath: "content",
documentTypes: [Post],
});3. 配置 Next.js
在 next.config.ts 中:
import { withContentlayer } from "next-contentlayer2";
export default withContentlayer({
// 你的 Next.js 配置
});创建内容
在 content/posts/ 目录下创建 MDX 文件:
---
title: My First Post
date: 2025-01-01
description: This is my first blog post
tags:
- nextjs
- blog
---
## Hello World
This is the content of my first post.
<Callout variant="note">This is a custom component in MDX!</Callout>在页面中使用
获取所有文章
import { allPosts } from 'contentlayer/generated'
export default function BlogPage() {
return (
<div>
{allPosts.map((post) => (
<article key={post._id}>
<h2>{post.title}</h2>
<p>{post.description}</p>
</article>
))}
</div>
)
}动态路由
// app/blog/[slug]/page.tsx
import { allPosts } from 'contentlayer/generated'
import { notFound } from 'next/navigation'
export default function PostPage({ params }: { params: { slug: string } }) {
const post = allPosts.find((p) => p.slug === params.slug)
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.body.html }} />
</article>
)
}高级特性
自定义 MDX 组件
// components/mdx-components.tsx
import type { MDXComponents } from "mdx/types";
import { Callout } from "@/components/callout";
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
Callout,
};
}计算字段
computedFields: {
readingTime: {
type: 'json',
resolve: (post) => readingTime(post.body.raw),
},
url: {
type: 'string',
resolve: (post) => `/blog/${post.slug}`,
},
}内容过滤和排序
const publishedPosts = allPosts
.filter((post) => post.status === "published")
.sort((a, b) => new Date(b.date) - new Date(a.date));性能优化
静态生成
export async function generateStaticParams() {
return allPosts.map((post) => ({
slug: post.slug,
}));
}按需生成
export const dynamicParams = true;
export const revalidate = 3600; // 每小时重新验证SEO 优化
export async function generateMetadata({ params }) {
const post = allPosts.find((p) => p.slug === params.slug);
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: "article",
},
};
}常见问题
Q: 如何处理图片?
A: 将图片放在 public 目录,或使用 next/image 组件。
Q: 支持代码高亮吗?
A: 是的,可以使用 rehype-pretty-code 插件。
Q: 如何实现搜索功能?
A: 可以在构建时生成搜索索引,或使用客户端搜索库如 FlexSearch。
总结
Contentlayer 为 Next.js 博客提供了强大的内容管理能力,结合类型安全和优秀的开发体验,是构建现代化博客的理想选择。
开始构建你的博客吧!
继续阅读
顺着文集继续
相关阅读