Written by
Fuma Nama
At
Sun Dec 15 2024
使用 Fumadocs 创建博客
Fumadocs + 博客
Fumadocs 是一个文档框架,同时也是一个强大的工具,用于管理 Next.js 中的内容。你可以使用 Fumadocs 在单个 Next.js 应用中构建博客网站和文档。
🌐 Fumadocs is a docs framework, but it's also a powerful tool to manage content in Next.js. You can use Fumadocs to build a blog site along with docs, on a single Next.js app.
概览
🌐 Overview
本指南将帮助你使用 Fumadocs 和 Fumadocs MDX 建立博客网站。
🌐 This guide helps you build a blog site with Fumadocs and Fumadocs MDX.
我们将使用 Fumadocs MDX 来管理内容,并使用 Tailwind CSS 和 Fumadocs UI 实现我们自己的用户界面。
🌐 We will use Fumadocs MDX to manage the content, and implement our own UI with Tailwind CSS & Fumadocs UI.
配置内容
🌐 Configure Content
定义一个 blogPosts 集合。
🌐 Define a blogPosts collection.
import { defineCollections, frontmatterSchema } from 'fumadocs-mdx/config';
import { z } from 'zod';
export const blogPosts = defineCollections({
type: 'doc',
dir: 'content/blog',
// add required frontmatter properties
schema: frontmatterSchema.extend({
author: z.string(),
date: z.string().date().or(z.date()),
}),
});解析 source.ts 中的输出集合:
🌐 Parse the output collection in source.ts:
import { loader } from 'fumadocs-core/source';
import { toFumadocsSource } from 'fumadocs-mdx/runtime/server';
import { blogPosts } from 'fumadocs-mdx:collections/server';
export const blog = loader({
baseUrl: '/blog',
source: toFumadocsSource(blogPosts, []),
});你现在可以访问 blog 的内容。
🌐 You can now access the content from blog.
实现用户界面
🌐 Implement UI
创建一个博客文章索引页。
🌐 Create an index page for blog posts.
默认情况下,应该有一个 (home) 路由组,里面包含 <HomeLayout />。
我们把博客页面放在里面,这样就可以在我们的博客网站上得到漂亮的导航栏。
🌐 By default, there should be a (home) route group with <HomeLayout /> inside.
Let's put the blog pages under it, this way we can get the nice navbar on our blog site.
import Link from 'next/link';
import { blog } from '@/lib/source';
export default function Home() {
const posts = blog.getPages();
return (
<main className="flex-1 w-full max-w-[1400px] mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8">Latest Blog Posts</h1>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<Link
key={post.url}
href={post.url}
className="block bg-fd-secondary rounded-lg shadow-md overflow-hidden p-6"
>
<h2 className="text-xl font-semibold mb-2">{post.data.title}</h2>
<p className="mb-4">{post.data.description}</p>
</Link>
))}
</div>
</main>
);
}值得了解
像 text-fd-muted-foreground 这样的颜色来自 Fumadocs UI 的设计系统,你也可以使用自己的颜色,或者使用 Shadcn UI。
🌐 Colors like text-fd-muted-foreground are from the design system of Fumadocs UI, you may also use your own colors, or use Shadcn UI.
并创建一个用于博客文章的页面。
🌐 And create a page for blog posts.
请注意,博客文章不会有像 /slug1/slug2 那样的嵌套路径。我们不需要为博客文章设置通用路由。
🌐 Note that blog posts won't have nested slugs like /slug1/slug2. We don't need a catch-all route for blog posts.
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { blog } from '@/lib/source';
export default async function Page(props: { params: Promise<{ slug: string }> }) {
const params = await props.params;
const page = blog.getPage([params.slug]);
if (!page) notFound();
const Mdx = page.data.body;
return (
<>
<div className="w-full max-w-[1400px] mx-auto px-4 py-12 rounded-xl border md:px-8">
<h1 className="mb-2 text-3xl font-bold">{page.data.title}</h1>
<p className="mb-4 text-fd-muted-foreground">{page.data.description}</p>
<Link href="/blog">Back</Link>
</div>
<article className="w-full max-w-[1400px] mx-auto flex flex-col px-4 py-8">
<div className="prose min-w-0">
<InlineTOC items={page.data.toc} />
<Mdx components={defaultMdxComponents} />
</div>
<div className="flex flex-col gap-4 text-sm">
<div>
<p className="mb-1 text-fd-muted-foreground">Written by</p>
<p className="font-medium">{page.data.author}</p>
</div>
<div>
<p className="mb-1 text-sm text-fd-muted-foreground">At</p>
<p className="font-medium">{new Date(page.data.date).toDateString()}</p>
</div>
</div>
</article>
</>
);
}
export function generateStaticParams(): { slug: string }[] {
return blog.getPages().map((page) => ({
slug: page.slugs[0],
}));
}并配置元数据:
🌐 And configure metadata:
import { notFound } from 'next/navigation';
import { blog } from '@/lib/source';
export async function generateMetadata(props: { params: Promise<{ slug: string }> }) {
const params = await props.params;
const page = blog.getPage([params.slug]);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}写帖子
🌐 Write Posts
界面现已完成,你可以在 content/blog 目录下编写一些博客文章,例如:
🌐 The UI is now complete, you can write some blog posts under the content/blog directory, like:
---
title: Hello World
author: Fuma Nama
date: 2024-12-15
---
## Hello World
This is an example!使用 next dev 启动开发服务器后,你应该可以在 /blog 路由下看到博客文章。
🌐 After spinning up the development server with next dev, you should see the blog posts under /blog route.