新增内容区域操作指南
如何在 Fumadocs 博客中新增一个独立的内容区域(新的内容源 + 对应的页面路由)
本文档说明如何在本博客中新增一个独立的内容区域(即一个新的内容源 + 对应的页面路由)。当前已有的内容区域是 docs(访问路径 /docs),以下以新增一个名为 articles(访问路径 /articles)的内容区域为例。
替换说明
如果你想用其他名称,将下文所有 articles 替换为你的名称即可(注意大小写一致)。
整体流程概览
需要修改/创建的文件共 6 处:
| 序号 | 文件 | 操作 |
|---|---|---|
| 1 | source.config.ts | 新增一个 defineDocs 导出 |
| 2 | lib/source.ts | 新增一个 source loader 导出 |
| 3 | app/articles/layout.tsx | 新建,页面布局 |
| 4 | app/articles/[[...slug]]/page.tsx | 新建,页面渲染 |
| 5 | content/articles/ | 新建文件夹,存放 mdx 文件 |
| 6 | (可选)导航栏添加链接 | 修改 lib/layout.shared.tsx |
第一步:修改 source.config.ts
在项目根目录的 source.config.ts 中,新增一个 defineDocs 调用。
修改前
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
import { transformerMetaHighlight } from '@shikijs/transformers';
export const docs = defineDocs({
dir: 'content/docs',
});
export default defineConfig({
mdxOptions: {
rehypeCodeOptions: {
transformers: [transformerMetaHighlight()],
} as any,
},
});修改后(新增部分已标注)
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
import { transformerMetaHighlight } from '@shikijs/transformers';
export const docs = defineDocs({
dir: 'content/docs',
});
export const articles = defineDocs({
dir: 'content/articles',
});
export default defineConfig({
mdxOptions: {
rehypeCodeOptions: {
transformers: [transformerMetaHighlight()],
} as any,
},
});第二步:修改 lib/source.ts
新增一个 source loader,用于将内容源转换为 Fumadocs 可用的页面树和路由数据。
修改前
import { createElement } from 'react';
import { icons as lucideIcons } from 'lucide-react';
import { docs } from 'collections/server';
import { loader } from 'fumadocs-core/source';
import { attachSidebarIcons } from '@/lib/sidebar-icons';
function resolveIcon(icon: string | undefined) {
if (!icon) return undefined;
const Component = (lucideIcons as Record<string, unknown>)[icon];
if (Component) return createElement(Component as React.ComponentType);
return undefined;
}
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
icon: resolveIcon,
});
attachSidebarIcons(source.pageTree);修改后(新增部分已标注)
import { createElement } from 'react';
import { icons as lucideIcons } from 'lucide-react';
import { docs, articles } from 'collections/server';
import { loader } from 'fumadocs-core/source';
import { attachSidebarIcons } from '@/lib/sidebar-icons';
function resolveIcon(icon: string | undefined) {
if (!icon) return undefined;
const Component = (lucideIcons as Record<string, unknown>)[icon];
if (Component) return createElement(Component as React.ComponentType);
return undefined;
}
export const source = loader({
baseUrl: '/docs',
source: docs.toFumadocsSource(),
icon: resolveIcon,
});
export const articlesSource = loader({
baseUrl: '/articles',
source: articles.toFumadocsSource(),
icon: resolveIcon,
});
attachSidebarIcons(source.pageTree);
attachSidebarIcons(articlesSource.pageTree);第三步:创建路由文件夹和文件
在 app/ 目录下创建对应的路由结构。文件夹名即为访问路径。
3.1 创建 app/articles/layout.tsx
import { articlesSource } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<DocsLayout
tree={articlesSource.pageTree}
{...baseOptions()}
>
{children}
</DocsLayout>
);
}3.2 创建 app/articles/[[...slug]]/page.tsx
import { articlesSource } from '@/lib/source';
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from 'fumadocs-ui/layouts/docs/page';
import { notFound } from 'next/navigation';
import { getMDXComponents } from '@/components/mdx';
import type { Metadata } from 'next';
import { createRelativeLink } from 'fumadocs-ui/mdx';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
}) {
const params = await props.params;
const page = articlesSource.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc} full={page.data.full} tableOfContent={{ style: 'clerk' }}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX
components={getMDXComponents({
a: createRelativeLink(articlesSource, page),
})}
/>
</DocsBody>
</DocsPage>
);
}
export async function generateStaticParams() {
return articlesSource.generateParams();
}
export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>;
}): Promise<Metadata> {
const params = await props.params;
const page = articlesSource.getPage(params.slug);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}要点:所有 source.xxx 替换为 articlesSource.xxx。这与 app/docs/[[...slug]]/page.tsx 结构完全一致,只是换了数据源。
第四步:创建内容文件夹和示例文件
4.1 文件夹结构
在 content/ 目录下创建 articles/ 文件夹及示例文件:
4.2 创建首页文件 content/articles/index.mdx
---
title: 文章
description: 这里是文章区域
---
## 欢迎来到文章区域
这里是新的内容区域,可以开始写你的文章了。4.3 meta.json 格式
最简格式:
{
"title": "分类名称"
}带图标的格式(需要 Lucide 图标支持):
{
"title": "分类名称",
"icon": "FolderIcon"
}第五步(可选):在导航栏添加入口
修改 lib/layout.shared.tsx,在 links 数组中添加导航链接:
links: [
{
text: '文章',
url: '/articles',
active: 'nested-url',
},
// ... 原有的其他链接
],或者如果导航栏使用的是 nav 配置中的其他字段,在对应位置添加即可。
第六步:重新构建
修改完成后,需要重启开发服务器(或重新构建),让 fumadocs-mdx 重新生成 .source/ 目录下的文件。
# 停止当前运行的开发服务器,然后重新启动
pnpm dev如果是生产环境构建:
pnpm build文件结构对照图
修改完成后的完整结构(标有 [新增] 的为新增部分):
常见问题
快速添加模板(复制即用)
如果你需要添加第三个、第四个内容源,按以下模板替换 NAME 为你的内容源名称。
source.config.ts -- 添加:
export const NAME = defineDocs({
dir: 'content/NAME',
});lib/source.ts -- 添加:
import { docs, NAME } from 'collections/server';
export const NAME_source = loader({
baseUrl: '/NAME',
source: NAME.toFumadocsSource(),
icon: resolveIcon,
});app/NAME/layout.tsx -- 新建:
import { NAME_source } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { baseOptions } from '@/lib/layout.shared';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<DocsLayout tree={NAME_source.pageTree} {...baseOptions()}>
{children}
</DocsLayout>
);
}app/NAME/[[...slug]]/page.tsx -- 新建:
import { NAME_source } from '@/lib/source';
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from 'fumadocs-ui/layouts/docs/page';
import { notFound } from 'next/navigation';
import { getMDXComponents } from '@/components/mdx';
import type { Metadata } from 'next';
import { createRelativeLink } from 'fumadocs-ui/mdx';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
}) {
const params = await props.params;
const page = NAME_source.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc} full={page.data.full} tableOfContent={{ style: 'clerk' }}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX
components={getMDXComponents({
a: createRelativeLink(NAME_source, page),
})}
/>
</DocsBody>
</DocsPage>
);
}
export async function generateStaticParams() {
return NAME_source.generateParams();
}
export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>;
}): Promise<Metadata> {
const params = await props.params;
const page = NAME_source.getPage(params.slug);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}content/NAME/index.mdx -- 新建:
---
title: NAME 首页
description: NAME 区域描述
---
## 欢迎来到 NAME
在这里开始写内容。然后重启开发服务器即可。