
深入了解如何使用 Contentlayer 和 Next.js 构建一个功能完整、性能优秀的博客系统
Contentlayer 是一个内容 SDK,它可以将 Markdown 和 MDX 文件转换为类型安全的 TypeScript 数据。它完美集成了 Next.js,让你可以像处理普通数据一样处理内容。
| 特性 | Contentlayer | MDX | Markdown Files |
|---|---|---|---|
| 类型安全 | ✅ | ❌ | ❌ |
| 零运行时 | ✅ | ❌ | ✅ |
| MDX 支持 | ✅ | ✅ | ❌ |
| 开发体验 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
npm install contentlayer2 next-contentlayer2创建 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],
})在 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>
)
}// 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 // 每小时重新验证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',
},
}
}A: 将图片放在 public 目录,或使用 next/image 组件。
A: 是的,可以使用 rehype-pretty-code 插件。
A: 可以在构建时生成搜索索引,或使用客户端搜索库如 FlexSearch。
Contentlayer 为 Next.js 博客提供了强大的内容管理能力,结合类型安全和优秀的开发体验,是构建现代化博客的理想选择。
开始构建你的博客吧!