文章

Astro 内容站从 0 到当前版本:内容模型、数据层、路由与构建配置总整理

把当前仓库里不属于单个组件的技术设计一次讲清:content collections、双语路径、系列数据模型、静态路径生成、Cloudflare 构建配置和 TypeScript 设置。

这篇专门整理当前仓库里那些“不属于某一个组件”的部分。

如果说前几篇在讲 Header、主题、滚动恢复、卡片和详情页这些组件,这一篇讲的就是整站骨架:从 0 开始把一个 Astro 站点推到现在这套结构,中间真正稳定下来的内容模型、数据流、路由和构建设置到底是什么。

1. 当前项目的技术底座

最外层配置现在集中在这些文件:

  • package.json
  • astro.config.mjs
  • tsconfig.json
  • wrangler.jsonc
  • worker-configuration.d.ts
  • src/env.d.ts

它们分别解决的是:

  • Astro 与 Cloudflare Workers 的依赖和脚本
  • Astro 的适配器、别名和 Markdown 代码高亮主题
  • TypeScript 严格模式与 @/* 路径别名
  • Workers 入口、静态资源绑定和兼容日期
  • Cloudflare 运行时类型
  • Astro 在 Cloudflare 运行时下的 App.Locals 类型

当前 astro.config.mjs 的关键点只有 3 个:

  • adapter: cloudflare()
  • @ 指向 src
  • Shiki 同时声明 github-lightgithub-dark

这让后面的页面、组件、内容渲染和部署都落到了同一条链路上。

2. 内容模型已经不止一套 articles

这个站点现在不是单一文章流,而是 5 个集合一起工作:

  • libraryArticles
  • librarySeriesEntries
  • librarySeries
  • works / worksEn
  • media / mediaEn
  • products / productsEn

其中:

  • libraryArticles 存中文文库单篇文章
  • librarySeries 存中文文库系列元数据
  • librarySeriesEntries 读取 library/series/*/* 下的章节文件
  • works* / media* / products* 存双语官网资产

这就是当前内容结构能同时支持:

  • 中文原文库
  • 双语作品页
  • 双语媒体页
  • 双语商店与授权页

src/content.config.ts 里还专门做了一个 defineMarkdownCollection(...),把重复的 loader 写法先收拢掉。到这一步,内容结构已经从“一个目录放所有 Markdown”变成了明确分流的内容系统。

3. 站点文案和路径不散落在页面里

当前仓库把路径和文案统一收在 src/site.ts

这里做了两件非常关键的事:

  1. 维护中英文 siteCopy
  2. 提供所有路径 helper

路径 helper 现在包括:

  • getHomePath
  • getAboutPath
  • getArchivePath
  • getArticlesPath
  • getSeriesPath
  • getSeriesChapterPath
  • getOppositeLocale

官网中英文路径前缀统一由 withLocalePrefix() 处理;文库路径则固定归入 /library/...。这样页面组件和数据层都不需要自己拼字符串。

4. 文章数据层已经形成了稳定接口

src/lib/library/articles.ts 负责文库文章这一条线。当前它已经把页面真正需要的动作都整理出来了:

  • getPublishedArticles()
  • getArticlesStaticPaths()
  • getRelatedArticles(entry, limit)
  • toArticleCardItems(entries, label)

也就是说,页面层不再直接和 getCollection("libraryArticles") 打交道,而是只消费“公开文章”“静态路径”“相关文章”“卡片数据”这些更高层的接口。

这一步非常重要,因为它把 Astro 内容集合原始数据,转成了页面真正能用的站点数据。

5. 系列数据层比文章线更复杂

src/lib/library/series.ts 是当前仓库最像“业务层”的文件。

系列系统之所以比文章复杂,是因为它不仅要读内容,还要解决:

  • 系列元数据和章节目录怎么对应
  • 章节 slug 怎么生成
  • 系列内章节顺序怎么确定
  • 章节列表里如何生成 badge
  • 如何得到上一章、下一章

当前版本的核心做法,是用 sourceFolder 把系列元数据与章节目录绑定起来。系列文件里显式写:

sourceFolder: "Not-Yet-Manifested"

章节则从文件路径中推导所属目录。这样系列和章节之间的关联,就不是靠文件名巧合,而是靠一条稳定映射关系。

6. 章节 slug 和顺序都在数据层完成

系列章节这一层,当前已经做了几条很关键的规则:

  • getChapterSourceKey() 提取章节源 key
  • getChapterSlug() 允许 frontmatter 覆盖默认 slug
  • sortSeriesEntries() 优先按前导数字排序,再按日期和 slug 排序
  • assertUniqueChapterSlugs() 防止同系列下 slug 冲突
  • getSeriesChapterContext() 生成当前章、上一章、下一章

这几条规则加起来,才让系列页真正具备了“系列目录”和“章节导航”。

7. 归档不是新内容源,而是聚合层

src/lib/library/archive.ts 并没有定义新 schema。它只做一件事:

  • 把单篇文章和系列章节合并成一个按发布时间排序的列表

这里的关键不是技术难度,而是边界意识。归档页不应该维护自己的内容来源,它应该只消费已经存在的两条内容流,然后再做聚合。

因此归档层保持得很轻:

  • library/articles.ts 取公开文章
  • library/series.ts 取公开系列及章节
  • 合并后统一排序

8. 路由文件被刻意做薄

当前 src/pages/ 下的路由文件几乎都很短,这是有意设计出来的结果。

静态页面通常只剩一句:

<HomePageView locale="zh" />

动态详情页只负责声明 getStaticPaths(),然后把 props 交给对应的 page view:

export async function getStaticPaths() {
  return getSeriesChapterStaticPaths();
}

这样做之后,路由层只表达两件事:

  • 当前页面对应哪个 page view
  • 动态路由需要哪些静态路径

剩下的内容获取、章节上下文、页面结构,全部放回组件层和数据层。

9. 从 0 到现在,真正完成的不是“页面变多了”

而是站点内部的分工稳定下来了。

从最初的 Astro + Cloudflare Workers 最小部署,到现在这套结构,中间实际上完成了 5 次抽象升级:

  1. 有了可部署的 Astro 工程
  2. 用 content collections 定义了结构化内容
  3. site.ts 统一了多语言文案和路径
  4. lib/* 建起了页面真正消费的数据层
  5. 用 page views 和组件层把路由、数据、视图拆开

所以当前版本的价值,不只是页面更多,而是整站已经从“几个页面文件”变成了“可继续扩展的内容站架构”。

10. 这篇和其他专题的关系

如果你要从 0 复刻到当前版本,阅读顺序其实可以这样看:

  1. 先看最简部署教程
  2. 再看这篇总整理,建立整站结构认识
  3. 最后按需要去看 Header、主题、滚动恢复、卡片列表、详情页外壳、系列目录这些组件专题

这样就不会再把所有改动揉在一篇文章里,也更接近当前仓库真实的技术分层。

相关文章

Astro 内容站:PageHero 组件与页面头部统一

把列表页、首页、系列页和详情页里重复的标题区抽成 PageHero,让返回链接、eyebrow、标题和状态文案共用一套骨架。

2026-04-16

Astro 内容站:PageSectionHeader 组件与区块标题复用

把 section 标题、补充说明、右侧动作和分组计数统一到一个组件里,让首页、归档和索引列表共享稳定的区块头结构。

2026-04-16

Astro 内容站:组件组合重构、重复代码清理与文档同步

一次收口页面骨架、分页辅助、概览卡和旧文档漂移,把页面组合整理成更稳定的单组件文件结构。

2026-04-16

Astro 内容站:SummaryStatGrid 组件与归档总览卡

把归档页里重复的统计卡结构抽成 SummaryStatGrid,为总条目、文章数、章节数和最近更新时间提供统一的数值卡组件。

2026-04-16