它起初只是一个 HTML 文件

open.awareride.com 最初只是一个静态 HTML 页面 – 一个 logo、一句标语、一个试了好几次才弄对的 favicon,以及一个把 GitHub Pages 指向自定义域名的 CNAME。它撑过一个下午,但撑不了多久。

计划很简单:为 AwareRide 及其项目建一个真正的家 – 一个像产品 的落地页、从各项目自身仓库拉取的产品文档,以及一个写工程文章的博客。我不想要一个“兼职当首页 的文档站”,我想要一个“顺带发布文档的首页”。

我选了 Astrooutput: 'static' 模式,再没回头。

四条路由,一个外壳

一切都挂在四条路由上:

  • / – 落地页
  • /packscope – 产品页
  • /packscope/docs – 文档,以 Markdown 形式写在 packscope 仓库里
  • /posts – 这个博客

一个 Layout.astro 拥有文档外壳(<html>、字体、meta、OG 标签)。产品页和文档页组合它,而不是 重新造一个 <head>DocsLayout.astro 组合 Layout + Nav + Footer,并加上侧边栏和 .prose 内容区。代码库里没有任何第二个文档外壳 – 仅这一条规则就消灭了一整类“为什么这页长得不一样”的 bug。

内容即数据,而非文件

Astro 的内容集合(content collections)把“文档放哪儿”变成了一个有类型的问题。src/content.config.ts 为每个集合定义一个 zod schema 和一个 glob 加载器,所以一个错误的 frontmatter 字段会让构建失败, 而不是悄悄上线:

const postSchema = z.object({
  title: z.string(),
  date: z.date(),
  description: z.string(),
  tags: z.array(z.string()).default([]),
  author: z.string().optional(),
  source: z.string().optional(),
  draft: z.boolean().default(false),
});

文档来自 src/content/docs/packscope/**,文章来自 src/content/posts/**。每条路由都是一个瘦瘦的 [...slug].astro catch-all,调用 render(entry) 并把 <Content /> 包进 .prose 容器。

刻意只用一个样式表

没有 Tailwind,没有 CSS-in-JS。一个 src/styles/global.css 满是 CSS 自定义属性 (--color-*--radius-*--shadow-*),文件头注释至今写着 Anthropic-inspired: clean, bold, minimal。代码高亮用 Shiki 的 css-variables 主题,所以语法配色遵循同一套调色板,而不是互相打架。

.prose 类是 Markdown 排版的唯一真相来源,而且它住在 global.css 里,而不是某个组件的 scoped <style> 里。这是让它可靠地作用于 Astro <Content /> 输出的唯一办法 – 一个小细节, 省下了大量“为什么我的列表样式没了”的时间。

把构建跑绿

npm run build 先跑 astro check 再构建到 dist/。大部分打磨都在追那些显而易见的问题:一个 继承了深色背景的页脚、一个配色与页面冲突的终端示例、三张加载不出图片的特性卡片。一个一个,变绿。

整个迁移落在一个提交里 – feat: migrate to Astro static site – 主页终于像个主页了。


这是“构建 open.awareride.com”系列的一部分。下一篇: 部署到 GitHub Pages 与 Cloudflare Pages, 接着是带逐页回退的两种语言, 最后是通过 Pull Request 同步的内容中心