橙宝书
端到端项目

项目八 网站导航站

用一个 Worker 渲染导航页,KV 存链接数据,Cache API 缓存页面,管理 API 写入后主动清缓存。

编辑与核验:橙宝书编辑团队 ·

PROJECT 08新手约 45 分钟产出:带管理 API 与边缘缓存的导航站

完成标准

首页从 KV 读取链接数据并渲染成分类导航页;页面被 Cache API 缓存,连续两次访问第二次命中缓存;带 ADMIN_KEY 的 POST/DELETE 请求写入 KV 并立即清缓存,下一次访问拿到新页面;非法 slug 与非 http(s) URL 被拒绝并返回 400。

前置条件与运行随书示例

需要 Node.js 22、pnpm 和已安装的仓库依赖。示例位于 examples/nav-site,本地运行时 Wrangler 会模拟 KV;先复制本地 Secret,再启动开发服务器:

cp examples/nav-site/.dev.vars.example examples/nav-site/.dev.vars
pnpm wrangler dev --config examples/nav-site/wrangler.jsonc

另一个终端运行 node --test examples/nav-site/test/index.test.mjs,应看到 8 个测试通过。随后按示例 README 的 curl 新增链接,响应状态应为 201;第一次 GET / 响应头为 x-nav-cache: MISS,紧接着的第二次应为 HIT;删除链接后再访问首页应重新出现 MISS 并包含最新数据。缺少 Authorization 头的写请求应得到 401,非法 slug 应得到 400 invalid_slug

示例认证边界

ADMIN_KEY 只用于演示服务端 Bearer 校验。公开部署必须把它放在 Worker Secret 里(见绑定与密钥),不能写进代码或提交到仓库,也不能放进浏览器请求。

架构:读取缓存与写入失效闭环

导航站是"读多写少"的典型场景,正好展示 KV 与 Cache API 的分工:KV 是数据源,Cache API 是边缘加速器,管理 API 是唯一的写入口。

路径行为一致性保证
GET /先查 caches.default,未命中再读 KV 渲染 HTML 并写入缓存缓存命中时不碰 KV
GET /api/links直接读 KV 返回 JSON,不缓存实时反映数据
POST /api/links校验 ADMIN_KEY 与输入,写 KV,删除缓存中的首页写入后下一次访问必为新页面
DELETE /api/links/{slug}校验后从 KV 移除,删除缓存中的首页同上

写入路径是唯一直言一致性的地方:先写 KV 再清缓存,顺序不能反。如果先清缓存再写 KV,两次操作之间到达的读请求会把旧数据重新填进缓存。

成熟项目参考

导航站是 Cloudflare 社区里被反复实现的场景,下列开源项目各自代表了不同思路,值得在动手前读一遍:

  • NavSphere(Star 800+):把 GitHub 仓库当作 CMS,导航数据存在仓库里,提供管理界面和一键部署到 Cloudflare 的流程。适合希望"不改代码、网页上管理链接"的团队。
  • CF-Worker-Dir(Star 650+):单文件 Worker,链接配置直接写死在代码常量里,改配置即改代码。2024 年后基本停更,但代码短、结构直白,适合入门时通读源码理解 Worker 渲染 HTML 的最小形态。
  • menav(Star 约 300):支持从浏览器书签导入,在构建时生成静态页面再部署。适合链接集合稳定、可以接受"改数据就重新构建"的个人用户。

本教程的示例与它们路线不同:不引入 GitHub CMS 或构建流水线,而是演示 KV 加 Cache API 的"动态数据 + 边缘缓存"闭环——数据可以随时通过 API 修改、缓存由代码主动失效,每一步都有可运行的测试验证,出问题时可以按尾部章节回滚。

数据模型与输入校验

KV 中只存一个键 nav:links,值是包含全量链接的 JSON。导航站链接总量小(数百条以内),单键读写比逐条建模更简单,也让"写入后清一次缓存"的语义清晰。所有用户输入在进 KV 前校验:

const SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;

function validateLink(input) {
  if (!SLUG.test(input.slug)) return { error: 'invalid_slug' };
  const parsed = new URL(input.url);
  if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
    return { error: 'invalid_url' };
  }
  // title 限 80 字符,category 限小写 slug 形态,note 限 200 字符
  return { value: { slug: input.slug, title: input.title, url: parsed.toString() } };
}

渲染时必须对 title、note 等字段做 HTML 转义,链接地址只允许 http:/https: 协议——这条校验同时挡掉了 javascript: 形态的注入。完整实现见 examples/nav-site/src/index.mjs,校验分支都有对应测试。

缓存策略与主动失效

首页响应带 cache-control: public, max-age=300,并写入 caches.default。写操作成功后调用 cache.delete() 主动失效,因此管理端改完链接立刻可见。

Cache API 是按机房生效的

Cache API 的缓存和 delete() 都只在处理该请求的数据中心生效,不保证全球同步。max-age 的 TTL 是兜底:即使某个机房的旧缓存没被清掉,也会在 TTL 到期后自然失效。需要秒级全球一致的页面不要用这套组合。

KV 本身是最终一致的,全球读取传播通常在一分钟内完成。对导航站这种场景,"边缘缓存 TTL 兜底 + 写后主动失效"已经足够;如果你的场景需要更强一致性,先读存储选择器再决定是否换 D1。

验证与排障

现象先检查恢复动作
401 unauthorized.dev.vars 中的 ADMIN_KEYAuthorization: Bearer 值是否一致重新复制本地模板,不把值提交到仓库
400 invalid_slug / invalid_urlslug 是否为小写字母数字连字符,URL 是否为 http(s)修正请求体后重试,不要放宽校验正则
改完链接首页仍是旧内容响应头 x-nav-cache 是否为 HIT确认写请求返回 2xx;本地重启 Wrangler 清模拟缓存
409 slug_conflict该 slug 是否已存在换 slug,或先 DELETE 再 POST
本地 KV 读不到数据是否用了 --config examples/nav-site/wrangler.jsonc 启动以示例配置重启 Wrangler

远程边界与回滚

创建远程 KV namespace、wrangler secret put ADMIN_KEY、把 wrangler.jsonc 里的占位 id 换成真实 id、以及 wrangler deploy 都会修改 Cloudflare 账户状态,本教程不自动执行。代码回滚用 wrangler rollback 或重新部署上一版本即可,但回滚不能撤销 KV 里已写入的数据,也不能收回已分发的 Secret。由于示例把全部数据放在单个 nav:links 键里,数据回滚等价于恢复该键的旧 JSON——重要变更前先导出备份;出现 Key 泄露风险时优先轮换 Secret,再恢复稳定代码并审计写入日志。

下一步:绑定与密钥

官方来源

这篇内容帮你完成目标了吗?

内测反馈只在当前浏览器生成,不会自动上传。

本页目录