品牌首页与文档博客如何共存:Orbit Studio 的 Fumadocs 架构实践
Orbit Studio 从零探索后,如何把品牌首页与结构化 Blog 分层,并划清自定义代码与 Fumadocs 通用能力的责任边界。
Orbit Studio 确实是从零开始做的。
最初,我们对 Fumadocs、Nextra 这些文档框架几乎没有概念。只是先有了一个“做个人工作室网站”的想法,然后让 Coding Agent 按当时能够描述清楚的需求,把首页、导航和内容页面逐步搭出来。
有新想法,就加一个页面;需要写文章,就接入 MDX;内容多起来,再增加侧栏、目录、语言切换和移动端工具。每一步都可以完成,局部效果也能不断打磨。
真正的问题在多轮迭代后才显现:网站拥有了越来越多组件,却没有天然形成一套稳定的整体规范。许多看似简单的通用能力,都需要项目自己持续处理响应式、状态同步、可访问性和视觉一致性。
后来发现 Fumadocs 这类成熟框架时,它的价值并不是“帮我们写出原本不会写的代码”,而是提供一套已经被反复验证的文档结构和组件关系。
Orbit Studio 最终做的,不是在成熟项目上补装一个文档框架,而是重新整理前面反复探索得到的需求,把品牌层和内容层放回更清楚的边界:
从零开始,不代表从一开始就知道该用什么
Orbit Studio 是 Leon 的产品工作室网站,也是 Orbit 长期方向的公开入口。
它的演进大致经历了四个阶段。
先把品牌首页做出来
最开始关注的是品牌感:Orbit Studio 应该传达怎样的气质,首页应该放什么,怎样用背景、字体和交互建立记忆点。
技术方案只是实现手段。Coding Agent 让页面很快可见,也让我们可以不断尝试 Grainient、DotField、主题切换和 TargetCursor。
开始承载结构化内容
当网站不再只是一个首页,Notes、项目复盘和资源内容开始进入。此时才逐渐出现:
- 本地 MDX;
- 中文和英文;
- 文章详情页;
- 内容分类;
- 图片、表格与自定义组件。
继续自建文档体验
为了让文章更好读,项目继续增加:
- 桌面 Sidebar;
- 页内目录;
- 移动端 Drawer;
- 搜索入口;
- 文章宽度控制;
- Callout、Figure 和 Gallery;
- 语言与主题控件。
这些能力都能借助 Coding Agent 实现,但每增加一项,项目也多承担一项长期责任。
重新认识成熟框架的价值
重新调研 Fumadocs、Nextra、Rspress 和 Starlight 后,我们才意识到:前面反复自建的许多能力,本来就是成熟文档框架长期处理的问题。
这次选择 Fumadocs,不是因为项目已经被某种技术栈绑定,也不是因为旧网站无法运行,而是因为最终需求已经变得清楚:
- 首页仍然需要鲜明的品牌表达;
- Blog 需要规范、稳定的文档阅读结构;
- 内容继续使用本地 MDX、Git 与 Coding Agent;
- 需要中英文与自定义领域组件;
- 不希望继续自己维护整套通用文档 UI。
Coding Agent 让自建变容易,也让过度建设变容易
Coding Agent 很擅长把一个明确需求变成代码。
“增加一个移动端目录”“让侧栏可以折叠”“给首页做一个玻璃导航”,这些任务单独看都不难。问题在于,连续完成几十个局部任务,并不会自动形成一致的系统。
成熟组件的价值往往藏在需求没有写出来的地方:
- 不同断点下怎样切换;
- 键盘和屏幕阅读器怎样操作;
- Header、Sidebar 和正文怎样共享尺寸;
- Drawer 打开时如何锁定滚动;
- 主题状态怎样跨页面保持;
- 内容树变化后导航怎样同步;
- 升级后哪些行为仍然由框架保证。
Agent 可以继续自建这些能力,但项目必须先意识到它们存在,并在每次修改后重新验证。
所以这次重构真正减少的,不是代码输入量,而是 Orbit Studio 自己需要定义和维护的系统行为。
旧方案的问题不是不能用,而是责任越来越多
自建 Notes 的早期方案已经可以完成:
- MDX 编译;
- 中英文文章;
- 文章详情页;
- 自定义 Callout、Figure 和 Gallery;
- 桌面侧栏和文章目录;
- 移动端阅读工具。
从功能上看,它并没有失败。
真正的问题是,随着内容增加,项目需要持续负责越来越多通用能力:
- 内容目录怎样生成;
- 文章如何排序;
- Sidebar 如何折叠;
- 移动端 Drawer 如何表现;
- 主题与语言控件怎样同步;
- 文章宽度如何保持稳定;
- OG 与 Metadata 如何生成;
- 机器如何读取同一份内容;
- 框架升级后这些行为是否仍然兼容。
如果继续沿这条路迭代,Orbit Studio 会逐渐拥有一套自制的文档框架。但“维护文档框架”并不是这个项目希望形成的能力。
重构目标:减少自己需要负责的部分
这次重构没有以功能数量作为目标,而是明确了几个非目标:
- 不复制旧版 Navbar、Sidebar 和整套全局 CSS;
- 不运行 Fumadocs CLI Customize;
- 不 fork 官方布局;
- 不接 CMS;
- 不启用 Ask AI;
- 不启用全站搜索;
- 不提前建设 Projects 和 Contact;
- 不为了“完整”迁移没有真实需求的功能。
这些限制看起来保守,却帮助项目把注意力集中在真正属于 Orbit Studio 的部分。
最终架构:同一个网站,两种页面职责
Next.js App Router
├── RootProvider(Fumadocs)
│ ├── i18n context
│ └── theme context
│
├── Home
│ ├── HomeLayout(Fumadocs)
│ │ ├── Header / responsive navigation
│ │ ├── language slot
│ │ └── theme slot
│ ├── Grainient + DotField(Orbit Studio)
│ ├── Hero(Orbit Studio)
│ └── TargetCursor(Orbit Studio)
│
└── Blog
├── DocsLayout(Fumadocs Notebook)
│ ├── Header
│ ├── Sidebar / Page Tree
│ └── Mobile Drawer
└── DocsPage(Fumadocs)
├── Breadcrumb / TOC / Footer / Article Meta
└── MDX
├── Markdown primitives(Fumadocs)
├── Callout / Card / Steps / Accordion(Mintlify OSS)
├── ResponsiveTable(Orbit Studio)
└── FontLibrary(Orbit Studio)这个结构有一个容易忽略的特点:Fumadocs 没有“接管整个网站”。
首页继续是 Orbit Studio 的品牌页面,Blog 则使用文档框架提供的阅读结构。二者共享 Provider、主题、语言和内容基础,但页面密度与视觉任务不同。
一张表说明组件归属
| 区域或能力 | 谁负责 | 为什么 |
|---|---|---|
| 首页和 Blog 的基础布局 | Fumadocs | 响应式结构和导航行为属于通用能力 |
| 首页动态背景 | Orbit Studio | 它是品牌资产,不应进入通用文档层 |
| 首页 Hero 与关键词交互 | Orbit Studio | 文案、节奏和交互表达品牌主张 |
| Blog Sidebar 与 Drawer | Fumadocs | 没有必要重复实现成熟交互 |
| Page Tree 与文章顺序 | Fumadocs 行为,Orbit 数据 | 框架生成结构,项目决定内容 |
| 文章 TOC、Breadcrumb、Footer | Fumadocs | 属于标准阅读能力 |
| 主题和语言状态 | Fumadocs Provider | 避免建立第二套全局状态 |
| 主题与语言按钮外观 | Orbit Studio slot | 保留品牌交互,同时不复制 Header |
| Markdown 排版 | Fumadocs | 普通内容优先使用默认组件 |
| Callout、Card、Steps、Accordion 等正文组件 | Mintlify OSS+Orbit 适配层 | 使用成熟表达组件,同时统一为当前主题 token 与旧 MDX 接口 |
| 字体库与响应式资料表 | Orbit Studio | 它们解决明确的领域问题 |
这张表不是一次实现清单,而是后续迭代的判断依据。新增功能时,先问它属于品牌、内容还是通用框架,而不是直接创建一个新组件。
默认优先,是最重要的工程原则
Orbit Studio 对 Fumadocs 的扩展顺序是:
- 使用默认组件和默认行为;
- 使用公开 props、内容数据或
meta.json; - 使用 layout slot 扩展局部 UI;
- 通用的正文表达优先复用 Mintlify 等成熟开源组件,并通过适配层统一接口和主题;
- 只有领域需求才新增 MDX 组件;
- 没有明确决策时,不复制或 fork 布局源码。
例如,语言和主题控件确实需要延续旧版 42 × 42px 的透明圆形按钮,但它们并没有因此变成一套自定义 Header。
项目通过 Fumadocs 的 languageSelect 和 themeSwitch slot 注入两个按钮,状态仍然由 RootProvider 管理。这样改变的是呈现,不是系统归属。
这是一个很实用的区分:
能通过插槽改变的,不要通过复制组件改变;能通过内容数据改变的,不要通过重写布局改变。
内容只维护一份 Source
内容系统最容易产生的另一类问题,是为了不同功能维护平行清单:
- Sidebar 一份文章顺序;
- Blog 首页一份文章列表;
- OG 一份 Metadata;
- LLM 输出再扫描一份文件;
- 中英文关系另写一份映射。
Orbit Studio 让 MDX 和 meta.json 进入统一 Source:
MDX + meta.json
↓
fumadocs-mdx
↓
fumadocs-core loader
↓
统一 Source
├── Page Tree → Notebook Sidebar
├── Page Body → DocsPage
├── TOC → 文章目录
├── Metadata → 页面与 OG
└── Markdown → llms.txt / 页面 Markdown内容结构发生变化时,Sidebar、文章页和机器可读输出一起变化。项目不再为每个消费者复制一份文章清单。
Notes / Reflections / Resources 是内容语义,不是 URL 负担
Blog 的内容分成三个栏目:
Notes:实践笔记、实验和技术选择;Reflections:项目复盘与长期思考;Resources:工具、资料和可查询内容。
在内容目录中,它们使用 Fumadocs folder group。分组会出现在侧栏,但不会被强制写进文章 URL。
content/docs/zh/
├── (notes)/
├── (reflections)/
└── (resources)/因此,一篇文章可以属于 Notes,URL 仍然保持简洁:
/blog/article-slug内容分类可以调整,公开链接不需要随之改变。这让“知识组织”和“URL 稳定性”不再互相绑死。
为什么主动关闭搜索和 Ask AI
Fumadocs 可以提供搜索和 AI 相关能力,但 Orbit Studio 当前没有开启。
原因不是它们没有价值,而是当前内容量还不足以证明这些复杂度值得存在:
- 六篇双语内容可以通过侧栏和目录直接发现;
- 搜索需要额外界面、索引或服务端能力;
- Ask AI 需要模型、费用、数据来源和失败边界;
- 首页的主要目标是建立品牌信号,不是暴露所有功能。
这体现了框架选型中的另一个原则:
框架提供的能力是可选项,不是上线清单。
当内容量、读者行为或使用场景变化后,可以重新评估;在此之前,关闭功能也是架构的一部分。
内容组件也需要清楚分层
阅读区现在包含三层组件:Fumadocs 负责 Markdown 基础排版和代码块;Mintlify OSS 负责 Callout、Card、Steps、Accordion 等通用表达;Orbit Studio 只保留解决特定内容问题的领域组件。
Mintlify 组件不会接管 Sidebar、TOC 或页面布局。项目通过一层兼容适配器保留原有 MDX 写法,再把颜色、圆角、间距和明暗主题映射到 .orbit-reading 阅读区。这样可以获得更丰富、统一的文章组件,又不把整站改造成 Mintlify 托管项目。
Orbit Studio 当前保留两个典型的领域能力。
ResponsiveTable
普通 Markdown 表格继续使用 Fumadocs 默认样式。只有确实无法在窄屏阅读的决策表、资料表和预览表,才通过 ResponsiveTable 在移动端重排。
它不复制数据,也不替代桌面表格,只解决一个具体的响应式问题。
FontLibrary
字体库包含搜索、筛选、预览、标签和官方来源。它已经超出普通 Markdown 的表达范围,因此作为领域 MDX 组件存在。
它使用 Fumadocs 的主题 token,却不假装自己是一个通用文档组件。
判断是否应该创建新的领域组件,可以问三个问题:
- Markdown 和默认组件真的无法表达吗?
- 这个能力会被多篇内容或长期资源使用吗?
- 它能否保持独立,而不要求修改布局内部结构?
三个答案都为“是”时,自定义才更可能值得。
这次重构得到的收益
内容结构更接近写作本身
新增文章主要变成:
- 在对应语言和分组中新建 MDX;
- 填写标题和描述;
- 把文件加入
meta.json; - 验证中英文、构建和页面。
不再需要为每篇文章修改多个组件和数据清单。
框架升级边界更清楚
官方 Header、Sidebar、Drawer 和 DocsPage 没有被复制。项目需要维护的主要是公开 props、slot 和少量领域组件。
品牌没有被文档主题吞没
首页仍然保留 Grainient、DotField、主句和 TargetCursor,Blog 则获得稳定的阅读环境。品牌表达和内容可用性不再争夺同一套页面。
仍然存在的代价
这不是一个“换上 Fumadocs 后一切自动完成”的故事。
实际代价包括:
- 需要理解 Fumadocs 的 Provider、Source、Loader、Layout 和 Page Tree;
- Fumadocs、Next.js、React 与 MDX 的版本组合需要一起验证;
- 双语内容仍然需要人工或 Agent 维护对应关系;
- 首页的透明导航等品牌细节仍需谨慎使用作用域样式;
- 框架默认视觉不可能完全等同于自定义设计系统;
- 领域组件一旦增多,仍然可能形成新的维护负担。
项目通过 types:check、生产构建和关键页面验收降低风险,但这些成本不会消失。
可以复用的架构检查表
如果你也在把文档框架接入个人网站,可以检查:
- 首页和内容页是否承担不同任务?
- 哪些是品牌资产,哪些是通用能力?
- 是否复制了框架已经提供的 Header 或 Sidebar?
- 内容、Sidebar、OG 和搜索是否使用同一来源?
- 自定义组件是否解决真实领域问题?
- 是否因为框架“支持”就开启了暂时不需要的功能?
- 离开当前维护者后,边界是否仍然能被理解?
- 升级框架时,哪些文件最可能发生冲突?
如果这些问题没有答案,再漂亮的初始页面也可能逐渐变成难以维护的混合系统。
最终判断
Orbit Studio 最终没有在品牌网站和文档站之间选择一个。
首页继续负责让人记住它,Blog 负责让内容被长期阅读和使用。Fumadocs 提供结构,Orbit Studio 保留表达;框架负责通用能力,项目只在真正需要的地方建立自己的组件。
选择 Fumadocs 只是一次技术决策。更长期的价值,是项目终于知道哪些部分不应该再由自己实现。