使用 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],
});

这个配置的主要特点:

  1. 多语言支持:博客文章按语言组织在 content/blog/{language}/ 目录中
  2. 自动日期管理:使用文件系统时间戳作为创建和修改日期
  3. 类型安全字段:所有字段都有适当的类型定义,提供更好的开发体验

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} />;
}

这个实现允许我们:

  1. 在 MDX 文件中使用自定义组件
  2. 利用 Next.js 优化的组件(如 Image)
  3. 在需要时添加客户端交互功能

要在 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];
}

这种方法的优势

  1. 类型安全:Contentlayer 为我们的内容生成 TypeScript 类型
  2. 自动日期管理:无需手动管理日期
  3. 易于维护:简单的文件结构和配置
  4. SEO 友好:带有适当时间戳的自动站点地图生成
  5. 开发者体验:通过类型提示和自动完成提供出色的 IDE 支持

总结

使用 Contentlayer 和 Next.js 构建多语言博客系统是一次愉快的体验。自动日期管理和类型安全功能显著减少了维护负担,而与 Next.js 的 App Router 的集成使系统快速且对 SEO 友好。