ARCHITECTURE
这个站怎么运作
内容是 git 仓库里的纯文本文件,一条命令编译成一整套静态产物,push 即部署。 没有 CMS、没有数据库、没有服务端运行时 —— 线上只有静态文件和 CDN。
这条约束是所有其它设计的根源:因为没有服务端,搜索必须在客户端做、端点必须是构建时落盘的文件、 不能有鉴权、也不可能实时依赖外部 API。
全景:从一个文件到线上
四层:内容源 → 契约 → 构建 → 产物,然后 push 到 CDN。 唯一需要人手写的是第一层。
① 内容源 · 唯一需要人手写的东西
src/content/<coll>/
中文内容
src/content/<coll>En/
英文平行版
src/data/*.json
简介 / 社交
src/i18n/ui.ts
UI 文案
│
② 契约层 · Zod schema
src/content/config.ts
缺字段 / 类型不对 → build 失败(不是线上出空白页)
│
pnpm build
③ 构建 · Astro
字段形状:src/lib/api.ts(zh / en 共用一份)
│
④ 产物 · 全部是静态文件
5 类内容 × 2 语言
共 60 篇 × 2
页面 HTML
每页都有中英两版 + 404
24 个端点类型
12 类 × 2 语言 → 120 个 JSON 文件
2 份 RSS
/rss.xml · /en/rss.xml
2 份 llms.txt
agent 自发现入口
sitemap · robots · _headers
含 hreflang / Content-Signal / CORS
│
git push origin main
Cloudflare Pages
1–2 分钟上线
│
人
浏览器
zhanglu.net · /en/
agent
JSON 端点
/api · /en/api
CLI
npm 包
npx zhanglu-net
爬虫
sitemap + robots
Content-Signal
5 类内容
每一类都有中英两份平行内容,数量 1:1。 一个集合 = 一个目录,一篇内容 = 一个 markdown 文件。
| 集合 | 篇数 | 页面 | 端点 | 备注 |
|---|---|---|---|---|
| projects 项目 | 8 × 2 | 列表 + 详情 | 列表 + 详情 | 带 loc / persona / cover |
| articles 文章 | 5 × 2 | 仅列表 | 仅列表 | 写作索引,链接指向原始出处 |
| presentations 展示 | 4 × 2 | 仅列表 | 仅列表 | 卡片直接跳外链 |
| skills Skills | 42 × 2 | 列表 + 详情 | 列表 + 详情 | 16 个自动同步 + 14 个手写 |
| weekly 周报 | 1 × 2 | 列表 + 详情 | 列表 + 详情 | 脱敏公开版 |
写一次,出现在 7 个地方
这是整个结构最实在的收益。以 src/content/projects/boss.md 为例 ——
写一个文件,自动出现在下面每一处,没有任何一处需要手动同步。
projects/boss.md
│ pnpm build
英文版 projectsEn/boss.md 同理再出 7 份
双语是怎么做的
路由
中文在根 /,英文在 /en/。资源、/api、外链、锚点都不加前缀 —— 只有页面路由加。
语言判定
组件里 getLangFromUrl(Astro.url) 自检,不靠 props 一层层传下去。
内容
平行 *En 集合,不是同集合加语言字段 —— 后者要在十几处消费端加过滤,漏一处就串语言。代价是内容写两份。
UI 文案
src/i18n/ui.ts 字典(zh / en 两套)。页面独有的长散文写在各自语言的页面文件里,不进字典。
首访自适应
按 navigator.language 跳转 —— 英文浏览器打开 zhanglu.net 会自动到 /en/。
手动切换
页头「中 / EN」把选择写进 localStorage,之后以你的选择为准,不再自动跳。
SEO
<html lang>、hreflang(zh-CN / en / x-default)、og:locale、分语言 RSS、sitemap i18n。
给机器读的那一层
站点内容同时以 JSON 形式发布,agent 不需要解析 HTML。详细用法在 /agents。
自发现入口
/llms.txt · /en/llms.txt
↓
manifest
/api/index.json —— counts + 全部端点 + 语言交叉链接
↓
列表类 · 8 个
projects · articles · presentations · skills · weekly · about · social · search
详情类 · 3 个(含 body_md 全文)
projects/{slug} · skills/{slug} · weekly/{slug}
- · 每个响应带
lang字段,可自查拿到的是哪种语言。 - · 不存在的路径返回真 404,状态码可直接用来判断。
- · CORS 由
public/_headers显式声明,不依赖托管层的默认行为。 - · 搜索是客户端的:一次拉完全部语料做 substring 匹配 —— 因为没有服务端。
几条约束,以及为什么
这些不是抽象原则,每条背后都有一次真实的返工。
没有服务端
线上只有静态文件。永远可用、零运维、构建即快照。所有其它设计都是这条的推论 —— 所以搜索在客户端做、端点是构建时落盘的文件、不做鉴权。
schema 强校验
Zod 定义在 src/content/config.ts。内容缺字段或类型不对,build 直接失败 —— 而不是线上出一个空白页。
字段只定义一次
所有端点的字段形状在 src/lib/api.ts,zh / en 共用同一份 builder。早期各端点内联字段,漂过一次:列表有 loc/persona/cover、详情没有。
会漂的数字 build 时算
本页所有计数、/agents 上的 CLI 行数与版本号,都是构建时读出来的。曾经写死「270 行」,实际早已 500+。
状态码可信
不存在的路径返回真 404,不是 200 + 首页。agent 可以直接用状态码判断端点存在与否。
表驱动
CLI 的 KINDS 表:加一种内容类型只改一处,list / get / 帮助文本自动跟上。
从改一个字到上线
改 src/content/... 或 src/data/...
│
├── pnpm dev 本地热更新看效果
├── pnpm build 必过(schema 校验 + 产物生成)
├── docs/dev-log/ 留一条过程记录
└── git push main → Cloudflare Pages → 1–2 分钟上线 完整操作规范在仓库的 AGENTS.md —— 那是给 AI agent 和未来的我看的权威指南,含 schema、踩过的坑、发版流程。 本页的架构文档版本:docs/architecture.md。