济川

§ 09 — 用料清单

这个站是怎么搭的

全部写下来,包括做错的地方。既然要卖「我会建站」这句话,站本身就得经得起翻。

技术栈

选型为什么
框架Astro,静态输出没加 client 指令的内容原样就是 HTML。这是架构性质,不是配置技巧
样式Tailwind CSS v4 + 手写 tokentoken 管两个表面,Tailwind 管其余
动效只用原生 CSSView Transitions 和 scroll-driven animations 跑在 compositor 上,零 JS 开销,也影响不到爬虫读到的东西
内容Astro Content Collections构建期预渲染
图标astro-icon,内联不用图标字体,不发 CDN 请求
客户端 JS0 字节每次构建都验,不是口号

字体

拉丁文的标题和界面用 Archivo(OFL 1.1),自托管。 它本来就是为印刷和导视标识设计的字体——工作室的名字也是从导视来的。 数字、读数和代码用 IBM Plex Mono(OFL 1.1)。 两个都做了子集化并从自己的域名提供,因为 CSP 只允许 font-src 'self'

中文正文走读者设备上的系统字体栈——苹方、鸿蒙黑体、微软雅黑。 这是刻意的:完整自托管一套中文字体是 MB 级的量,加载时的字体替换肉眼可见; 而中国用户设备上的系统中文字体质量本来就够好。 真需要特色中文字体的地方用 霞鹜文楷(OFL 1.1), 按实际用到的字精确子集化,几 KB 而已。

没有用 Geist。它的许可没问题,问题是辨识度——一上脸就知道出处。 同样的理由排除了 shadcn 的默认配色。

部署

静态产物由洛杉矶一台 VPS 提供,走 Cloudflare Tunnel,最外层是 Cloudflare 代理。 API 挂在同一个源的 /api/ 下,反代到一个手写的 Node 服务—— 同源意味着不用配 CORS,也没有跨站 cookie 的麻烦。

没有 CI。上线闸放在 deploy.sh 里,这是一个人的项目真能维护得住的地方。

发布之前跑什么

scripts/geo-check.mjs 跑在 dist/ 上——检查的是真实产物不是源码—— 任何一项致命就退出码非零:

  1. 逐页统计删掉 script/style 后的正文字数,卡下限
  2. 客户端 JavaScript 字节数,以及有没有外部脚本引用
  3. canonical 是否存在、唯一、且与本页 URL 一致
  4. hreflang 是否双向互指,x-default 是否落在真实页面
  5. 每段 JSON-LD 能否解析,inLanguage<html lang> 是否相符
  6. 站内链接是否都指得到
  7. 每页一个 h1,title 与 description 齐全
  8. 对渲染后的文案做违禁词扫描
  9. sitemap 与 canonical 集合对账
  10. robots.txt 是否列出了该列的爬虫

做错的地方,留档

把字节当字符数

早期量自己生产站的正文,报出来是 14,524 字符。两个错叠在一起:wc -c 数的是字节;而只删 <script> 标签、 不删它内容的正则,会把 JSON-LD 和内联 JavaScript 的文本一起算进正文。 真实数字是 3,999。由此得出的「已接近满分」这个结论, 也跟着下调成了「结构合格、语料几乎为零」。

canonical 和 sitemap 差了一个斜杠

第一次绿灯的构建里,canonical 写的是 /zh,sitemap 写的是 /zh/。 canonical 指向一个会被重定向的地址,比不写 canonical 更糟。 构建输出里没有任何迹象——是把两个集合拿来对账才发现的。 上面第九项检查就是因为这个才有的。

差点抄了一份拦错东西的黑名单

一轮调研推荐了某个知名 AI 爬虫仓库里的 nginx-block-ai-bots.conf, 说它能拦训练类爬虫、同时放行引用类爬虫。把那十二行读完就会发现, 它是一条无差别黑名单:GPTBot、ChatGPT-User、ClaudeBot、Claude-User、 Claude-SearchBot 全部返回 403。 我们只从那个仓库取 UA 名单,其余一概不取。

用到的开源

Astro、Tailwind CSS、@astrojs/sitemap@astrojs/mdxastro-icon、Archivo、IBM Plex Mono、霞鹜文楷, 以及 ai-robots-txt/ai.robots.txt 的爬虫 UA 名单。

许可在采纳时逐个核过,部署时再卡一道——338 个生产依赖,全部宽松许可。 白名单保持严格,copyleft 一律不进;确实需要放行的走具名例外, 而不是放宽白名单本身。放宽了,下一个撞进来的 copyleft 包就会静默通过, 这道闸也就不再是闸了。

目前有一条这样的例外。@img/sharp-*Apache-2.0 AND LGPL-3.0-or-later——LGPL 那一半来自预编译二进制里 打包的 libvips,Astro 的图片处理用它。它只在构建期跑,库本身不被分发, LGPL 允许链接使用而不触及自有源码,而且一张被缩放过的图片 不是缩放器的衍生作品。