使用 Next.js 和 Contentlayer 构建多语言博客系统
7 min read
·在这篇文章中,我将分享如何使用 Next.js 和 Contentlayer 构建多语言博客系统。我们将从项目设置开始,一直到实现自动日期管理和动态站点地图生成。
为什么选择 Contentlayer?
在使用 Next.js 构建博客时,有几种处理 MDX 内容的方法:
- 直接使用
@next/mdx
- 使用无头 CMS
- 使用 Contentlayer
我选择 Contentlayer 是因为它提供:
- 类型安全的内容
- 出色的 MDX 支持
- 快速的构建时间
- 与 Next.js 的简单集成
项目设置
首先,我们需要安装必要的依赖:
pnpm add contentlayer next-contentlayer date-fns
然后,更新 next.config.js
以使用 Contentlayer:
import { withContentlayer } from 'next-contentlayer';
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withContentlayer(nextConfig);
配置 Contentlayer
我们的博客系统的核心是 Contentlayer 配置。以下是我们的设置方式:
import { defineDocumentType, makeSource } from 'contentlayer/source-files';
import { statSync } from 'fs';
import { join } from 'path';
const computedFields = {
language: {
type: 'string',
resolve: (doc) => doc._raw.flattenedPath.split('/')[1],
},
createdAt: {
type: 'date',
resolve: (doc) => {
const stats = statSync(join('./content', doc._raw.sourceFilePath));
return stats.birthtime;
},
},
updatedAt: {
type: 'date',
resolve: (doc) => {
const stats = statSync(join('./content', doc._raw.sourceFilePath));
return stats.mtime;
},
},
};
export const Blog = defineDocumentType(() => ({
name: 'Blog',
filePathPattern: 'blog/**/*.mdx',
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
description: { type: 'string' },
slug: { type: 'string', required: true },
},
computedFields,
}));
export default makeSource({
contentDirPath: './content',
documentTypes: [Blog],
});
这个配置的主要特点:
- 多语言支持:博客文章按语言组织在
content/blog/{language}/
目录中 - 自动日期管理:使用文件系统时间戳作为创建和修改日期
- 类型安全字段:所有字段都有适当的类型定义,提供更好的开发体验
TypeScript 配置
为了获得更好的类型安全性和开发体验,我们需要配置 TypeScript。更新你的 tsconfig.json
:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
]
}
这个配置确保 TypeScript 能够识别生成的 Contentlayer 类型并启用路径别名。
实现 MDX 组件
要使用自定义组件渲染 MDX 内容,我们创建一个客户端 MDX 组件:
'use client';
import Image from 'next/image';
import { useMDXComponent } from 'next-contentlayer/hooks';
// 定义在 MDX 文件中使用的自定义组件
const components = {
Image, // 使用 Next.js 的 Image 组件来优化图片
// 在这里添加更多自定义组件
};
interface MdxProps {
code: string;
}
export function Mdx({ code }: MdxProps) {
const Component = useMDXComponent(code);
return <Component components={components} />;
}
这个实现允许我们:
- 在 MDX 文件中使用自定义组件
- 利用 Next.js 优化的组件(如 Image)
- 在需要时添加客户端交互功能
要在 MDX 文件中使用自定义组件,只需将它们作为 JSX 元素使用:
<Image src="/path/to/image.jpg" alt="我的图片" width={800} height={400} />
博客文章结构
博客文章以 MDX 文件存储,使用简单的 frontmatter:
---
title: 文章标题
description: 简短描述
slug: article-slug
type: Blog
---
注意我们不需要手动管理日期 - 它们由文件系统自动处理!
渲染博客文章
以下是我们在 Next.js 页面中渲染博客文章的方式:
import { allBlogs } from 'contentlayer/generated';
import { Mdx } from '@/components/mdx-components';
export default function BlogPost({ params }) {
const post = allBlogs.find(
(post) => post.language === params.language && post.slug === params.slug
);
if (!post) notFound();
return (
<article className="prose dark:prose-invert max-w-none">
<h1>{post.title}</h1>
<Mdx code={post.body.code} />
</article>
);
}
动态站点地图生成
我们还实现了使用博客文章时间戳的动态站点地图生成:
import { allBlogs } from 'contentlayer/generated';
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://yuanzhixiang.com';
const blogUrls = allBlogs.map((blog) => ({
url: `${baseUrl}/${blog.language}/blog/${blog.slug}`,
lastModified: blog.updatedAt,
changeFrequency: 'weekly',
priority: 0.7,
}));
const staticUrls = ['en', 'zh'].map((lang) => ({
url: `${baseUrl}/${lang}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
}));
return [...staticUrls, ...blogUrls];
}
这种方法的优势
- 类型安全:Contentlayer 为我们的内容生成 TypeScript 类型
- 自动日期管理:无需手动管理日期
- 易于维护:简单的文件结构和配置
- SEO 友好:带有适当时间戳的自动站点地图生成
- 开发者体验:通过类型提示和自动完成提供出色的 IDE 支持
总结
使用 Contentlayer 和 Next.js 构建多语言博客系统是一次愉快的体验。自动日期管理和类型安全功能显著减少了维护负担,而与 Next.js 的 App Router 的集成使系统快速且对 SEO 友好。