2025-01-25 · 4 min read
使用 Contentlayer 构建现代化博客
#contentlayer#nextjs#blog#content-management
什么是 Contentlayer?
Contentlayer 是一个内容 SDK,它可以将 Markdown 和 MDX 文件转换为类型安全的 TypeScript 数据。它完美集成了 Next.js,让你可以像处理普通数据一样处理内容。
为什么选择 Contentlayer?
优势
- 类型安全:自动生成 TypeScript 类型
- 零运行时开销:构建时处理,运行时无额外成本
- MDX 支持:在 Markdown 中使用 React 组件
- 开发体验:热重载、类型提示、自动补全
- 灵活性:支持自定义字段和计算字段
与其他方案对比
| 特性 | Contentlayer | MDX | Markdown Files |
|---|---|---|---|
| 类型安全 | ✅ | ❌ | ❌ |
| 零运行时 | ✅ | ❌ | ✅ |
| MDX 支持 | ✅ | ✅ | ❌ |
| 开发体验 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
安装和配置
1. 安装依赖
bash
npm install contentlayer2 next-contentlayer22. 配置 Contentlayer
创建 contentlayer.config.ts:
typescript
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 中:
typescript
import { withContentlayer } from 'next-contentlayer2'
export default withContentlayer({
// 你的 Next.js 配置
})创建内容
在 content/posts/ 目录下创建 MDX 文件:
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>在页面中使用
获取所有文章
typescript
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>
)
}动态路由
typescript
// 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 组件
typescript
// components/mdx-components.tsx
import type { MDXComponents } from 'mdx/types'
import { Callout } from '@/components/callout'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
Callout,
}
}计算字段
typescript
computedFields: {
readingTime: {
type: 'json',
resolve: (post) => readingTime(post.body.raw),
},
url: {
type: 'string',
resolve: (post) => `/blog/${post.slug}`,
},
}内容过滤和排序
typescript
const publishedPosts = allPosts
.filter((post) => post.status === 'published')
.sort((a, b) => new Date(b.date) - new Date(a.date))性能优化
静态生成
typescript
export async function generateStaticParams() {
return allPosts.map((post) => ({
slug: post.slug,
}))
}按需生成
typescript
export const dynamicParams = true
export const revalidate = 3600 // 每小时重新验证SEO 优化
typescript
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 博客提供了强大的内容管理能力,结合类型安全和优秀的开发体验,是构建现代化博客的理想选择。
开始构建你的博客吧!