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 组件
  • 开发体验:热重载、类型提示、自动补全
  • 灵活性:支持自定义字段和计算字段

与其他方案对比

特性ContentlayerMDXMarkdown Files
类型安全
零运行时
MDX 支持
开发体验⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

安装和配置

1. 安装依赖

bash
npm install contentlayer2 next-contentlayer2

2. 配置 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 博客提供了强大的内容管理能力,结合类型安全和优秀的开发体验,是构建现代化博客的理想选择。

开始构建你的博客吧!

TypeScript 实用技巧和最佳实践Hello Contentlayer

Related posts