品牌首页与文档博客如何共存:Orbit Studio 的 Fumadocs 架构实践

Orbit Studio 从零探索后,如何把品牌首页与结构化 Blog 分层,并划清自定义代码与 Fumadocs 通用能力的责任边界。

Orbit Studio 确实是从零开始做的。

最初,我们对 Fumadocs、Nextra 这些文档框架几乎没有概念。只是先有了一个“做个人工作室网站”的想法,然后让 Coding Agent 按当时能够描述清楚的需求,把首页、导航和内容页面逐步搭出来。

有新想法,就加一个页面;需要写文章,就接入 MDX;内容多起来,再增加侧栏、目录、语言切换和移动端工具。每一步都可以完成,局部效果也能不断打磨。

真正的问题在多轮迭代后才显现:网站拥有了越来越多组件,却没有天然形成一套稳定的整体规范。许多看似简单的通用能力,都需要项目自己持续处理响应式、状态同步、可访问性和视觉一致性。

后来发现 Fumadocs 这类成熟框架时,它的价值并不是“帮我们写出原本不会写的代码”,而是提供一套已经被反复验证的文档结构和组件关系。

Orbit Studio 最终做的,不是在成熟项目上补装一个文档框架,而是重新整理前面反复探索得到的需求,把品牌层和内容层放回更清楚的边界:

先看结论
  • 品牌表达由 Orbit Studio 负责;布局、导航和目录等通用文档能力继续交给 Fumadocs。
  • 首页与 Blog 共用主题、语言和内容基础,但分别承担品牌记忆与稳定阅读两种任务。
  • 自定义代码只解决明确的领域问题;能够通过默认组件、公开 props 和 slot 完成的能力不再重复建设。

从零开始,不代表从一开始就知道该用什么

Orbit Studio 是 Leon 的产品工作室网站,也是 Orbit 长期方向的公开入口。

它的演进大致经历了四个阶段。

1

先把品牌首页做出来

最开始关注的是品牌感:Orbit Studio 应该传达怎样的气质,首页应该放什么,怎样用背景、字体和交互建立记忆点。

技术方案只是实现手段。Coding Agent 让页面很快可见,也让我们可以不断尝试 Grainient、DotField、主题切换和 TargetCursor。

2

开始承载结构化内容

当网站不再只是一个首页,Notes、项目复盘和资源内容开始进入。此时才逐渐出现:

  • 本地 MDX;
  • 中文和英文;
  • 文章详情页;
  • 内容分类;
  • 图片、表格与自定义组件。
3

继续自建文档体验

为了让文章更好读,项目继续增加:

  • 桌面 Sidebar;
  • 页内目录;
  • 移动端 Drawer;
  • 搜索入口;
  • 文章宽度控制;
  • Callout、Figure 和 Gallery;
  • 语言与主题控件。

这些能力都能借助 Coding Agent 实现,但每增加一项,项目也多承担一项长期责任。

4

重新认识成熟框架的价值

重新调研 Fumadocs、Nextra、Rspress 和 Starlight 后,我们才意识到:前面反复自建的许多能力,本来就是成熟文档框架长期处理的问题。

这次选择 Fumadocs,不是因为项目已经被某种技术栈绑定,也不是因为旧网站无法运行,而是因为最终需求已经变得清楚:

  1. 首页仍然需要鲜明的品牌表达;
  2. Blog 需要规范、稳定的文档阅读结构;
  3. 内容继续使用本地 MDX、Git 与 Coding Agent;
  4. 需要中英文与自定义领域组件;
  5. 不希望继续自己维护整套通用文档 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 与 DrawerFumadocs没有必要重复实现成熟交互
Page Tree 与文章顺序Fumadocs 行为,Orbit 数据框架生成结构,项目决定内容
文章 TOC、Breadcrumb、FooterFumadocs属于标准阅读能力
主题和语言状态Fumadocs Provider避免建立第二套全局状态
主题与语言按钮外观Orbit Studio slot保留品牌交互,同时不复制 Header
Markdown 排版Fumadocs普通内容优先使用默认组件
Callout、Card、Steps、Accordion 等正文组件Mintlify OSS+Orbit 适配层使用成熟表达组件,同时统一为当前主题 token 与旧 MDX 接口
字体库与响应式资料表Orbit Studio它们解决明确的领域问题

这张表不是一次实现清单,而是后续迭代的判断依据。新增功能时,先问它属于品牌、内容还是通用框架,而不是直接创建一个新组件。

默认优先,是最重要的工程原则

Orbit Studio 对 Fumadocs 的扩展顺序是:

  1. 使用默认组件和默认行为;
  2. 使用公开 props、内容数据或 meta.json
  3. 使用 layout slot 扩展局部 UI;
  4. 通用的正文表达优先复用 Mintlify 等成熟开源组件,并通过适配层统一接口和主题;
  5. 只有领域需求才新增 MDX 组件;
  6. 没有明确决策时,不复制或 fork 布局源码。

例如,语言和主题控件确实需要延续旧版 42 × 42px 的透明圆形按钮,但它们并没有因此变成一套自定义 Header。

项目通过 Fumadocs 的 languageSelectthemeSwitch 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,却不假装自己是一个通用文档组件。

判断是否应该创建新的领域组件,可以问三个问题:

  1. Markdown 和默认组件真的无法表达吗?
  2. 这个能力会被多篇内容或长期资源使用吗?
  3. 它能否保持独立,而不要求修改布局内部结构?

三个答案都为“是”时,自定义才更可能值得。

这次重构得到的收益

内容结构更接近写作本身

新增文章主要变成:

  1. 在对应语言和分组中新建 MDX;
  2. 填写标题和描述;
  3. 把文件加入 meta.json
  4. 验证中英文、构建和页面。

不再需要为每篇文章修改多个组件和数据清单。

框架升级边界更清楚

官方 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 只是一次技术决策。更长期的价值,是项目终于知道哪些部分不应该再由自己实现。


主要参考

On this page