博客

飞导航如何准备迁移到 Cloudflare Pages

新版飞导航已经完成构建、响应头、重定向和 favicon 静态资源配置,接下来可以部署到 Cloudflare。

飞导航原来部署在 Vercel,新版准备迁到 Cloudflare Pages。页面域名会在 Pages 部署完成后切换,仓库已经收拾好:构建能重复,分支能预览,出问题也退得回去。

下面只记录飞导航现在用到的配置和上线顺序,不写通用的 Cloudflare Pages 入门教程。

Astro 输出纯静态文件

飞导航不需要服务端渲染。astro.config.mjs 明确使用静态输出:

export default defineConfig({
  site: "https://fei.im",
  output: "static",
  integrations: [icon()],
  build: {
    assets: "assets",
  },
});

site 用来生成 canonical 等绝对地址,构建产物写入 dist/。Cloudflare Pages 只托管这个目录,不运行 Astro 服务器。

正式构建命令是:

npm run build

这条命令还会先迁移链接,再做类型检查:

{
  "prebuild": "npm run migrate:links",
  "build": "astro check && astro build"
}

如果 YAML 解析失败、链接数量不对或文章 frontmatter 不符合结构,部署会在生成页面之前停止。

Cloudflare Pages 的构建设置

连接 Git 仓库后,Pages 只需要填写:

Build command: npm run build
Build output directory: dist

项目没用 Pages Functions,也没有数据库和服务端环境变量。导航和博客都在构建阶段生成。

仓库还提供了直接部署脚本:

{
  "deploy:pages": "npm run build && wrangler pages deploy dist --project-name=fei-im"
}

这个命令留给手动发布。正式环境会让 Pages 连接 GitHub:分支提交生成预览地址,合并主分支后再发布。

页面与 favicon 一起发布

飞导航主站只需要一个 Cloudflare 服务:

  1. Astro 生成 fei.im 静态页面;
  2. 构建脚本把 favicon 生成到 public/favicons/
  3. Cloudflare Static Assets 一次发布页面和图标。

独立的 fei-favicon 项目仍保留给其他项目复用:

workers/favicon/wrangler.jsonc

飞导航本身不再依赖它。只有其他项目需要更新 icons.fei.im 时才运行:

npm run deploy:favicon

飞导航页面现在请求:

/favicons/{domain}.png

npm run build 会先同步图标,再生成页面,因此不需要协调两个部署的先后顺序。

本地开发如何生成图标

直接启动主站:

npm run dev

predev 会先迁移链接,并把图标同步到 public/favicons/。不再需要环境变量或第二个本地服务。

_headers 里放了什么

Cloudflare Pages 会读取 public/_headers。当前全站响应头包括:

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Content Security Policy 只允许页面加载自己的图片,并允许搜索表单提交到 Google、Bing 和 Grok:

default-src 'self';
form-action https://www.google.com https://www.bing.com https://grok.com;
img-src 'self' data:;
object-src 'none';

以后加统计脚本、评论或外部图片时,CSP 也要跟着改。本地开发不会执行这份 Pages 响应头,漏改后通常要到预览环境才会暴露。

不同文件使用不同缓存

Astro 生成的 /assets/* 文件带内容哈希。文件内容变化时,文件名也会改变,因此可以缓存一年:

/assets/*
  Cache-Control: public, max-age=31536000, immutable

品牌图标和站点图标使用一周缓存:

/brand/*
  Cache-Control: public, max-age=604800

/icons/*
  Cache-Control: public, max-age=604800

HTML 没有设置长期缓存。发布新文章或修改导航链接后,访问者不应该继续拿到旧首页。

旧地址如何处理

public/_redirects 目前包含旧首页和关于页的兼容规则。博客上线后,文章使用固定 Markdown 文件名作为路径,例如:

src/content/blog/favicon-worker.md
→ /blog/favicon-worker/

文件名公开后就别随便改。确实要换路径,就在 _redirects 里给旧地址留跳转,否则搜索结果和外部引用都会断。

站点地图由 @astrojs/sitemap 在构建时自动生成。sitemap-index.xml 指向实际页面清单,新增文章后无需再手工维护 XML。

为什么继续使用原仓库

新版仍是飞导航,历史提交也有用,所以继续使用原仓库。

计划中的发布方式是:

  1. 旧 Hugo 版本留在 Astro 合并前的 Git 历史中;
  2. Astro 改动先放在独立开发分支;
  3. 让 Cloudflare Pages 部署该分支的预览;
  4. 检查导航、博客、404、响应头和 favicon;
  5. 合并到主分支;
  6. 最后切换 fei.im 域名。

真出问题时直接回到旧标签,不需要强制推送,也不会覆盖仓库历史。

正式切换前要验证什么

当前构建会生成 8 个页面:

  • 首页;
  • 关于页;
  • 博客列表;
  • 5 篇博客文章;
  • 404 页面。

构建成功只是第一关,上线前还要检查:

  • 首页 243 条链接是否完整;
  • Google、Bing、Grok 搜索是否指向正确地址;
  • 分类锚点是否能定位到对应区域;
  • 手机端是否出现横向页面滚动;
  • /favicons/{domain}.png 是否从主站返回图片;
  • 博客文章直接访问是否为 200;
  • 旧地址是否按预期重定向;
  • sitemap 中是否包含所有公开文章。

页面和图标属于同一个主站部署;fei-favicon 是留给其他项目的独立服务,不参与飞导航上线。