- token 重生成:--clay-500 品牌主色从占位红 #FF0000 还原为 clay #B96A3D (usercenter public + website tokens.gen) - usercenter package-lock:next / eslint-config-next 从 <14.3.0 固定到 14.2.35 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEHzjEcFzvGwgbxT6Wbt6c
穿山甲 · Pangolin 官网(Astro 纯静态 / SSG)
获客入口官网,从 design/ui_kits/website/ 迁移到 Astro 纯静态站点(零 SSR)。
设计目标:极致加载性能 + 整站可秒级复制到任意备用域名(灾备关键,doc/01 §1、doc/05)。
- 完全静态、无 API 依赖、无第三方统计/CDN(隐私承诺)。
- i18n 双路由单显:
/(中文)、/en/(English),不并排(设计铁律 6)。 - 与后端全部任务解耦,可独立并行开发与部署。
快速开始
cd web/website
npm ci # 干净、可复现安装(含自托管字体 @fontsource、Astro、React)
npm run dev # 本地预览 http://localhost:4321
npm run build # 产物到 dist/(含 token 同源生成 + CSP 哈希注入)
npm run preview # 预览构建产物
要求 Node ^18.20.8 || ^20.3.0 || >=22.0.0。
常用脚本
| 命令 | 作用 |
|---|---|
npm run build |
构建静态产物到 dist/(prebuild 生成令牌,postbuild 注入 CSP 哈希) |
npm run lint |
astro check 类型检查 |
npm run scan:redline |
扫描 dist/ 红线词(脱敏铁律 13) |
npm run hash:dist |
打印 dist/ 确定性指纹(主站/镜像一致性比对) |
npm test |
一键:生成令牌 → 构建 → CSP 哈希 → 红线扫描 |
目录结构
web/website/
├─ src/
│ ├─ layouts/Site.astro 整页布局(按 lang 单显),两路由复用
│ ├─ pages/index.astro / → lang="zh"
│ ├─ pages/en/index.astro /en/ → lang="en"
│ ├─ components/
│ │ ├─ *.jsx 交互组件(React island,@astrojs/react):
│ │ │ Header / AnnouncementBar / SignupForm / PricingPlans
│ │ ├─ *.astro 纯展示块(构建期渲染,零运行时 JS)
│ │ ├─ Icon.astro Lucide 图标 → 构建期内联 SVG(替代原型的 unpkg CDN)
│ │ └─ Brand.astro 品牌「行走穿山甲」内联 SVG
│ ├─ i18n/strings.ts 中/EN 字典(逐字沿用 ui_kits/website/site.js 脱敏文案)
│ └─ styles/
│ ├─ tokens.gen.css 由 design/colors_and_type.css 生成(见下)
│ ├─ website.css 逐字迁自 ui_kits/website/website.css
│ └─ site-extra.css 仅承载「原型内联 style= → 等价 class」
├─ public/_headers Cloudflare Pages 安全头(严格 CSP + HSTS)
├─ scripts/
│ ├─ build-tokens.mjs 令牌同源生成器
│ ├─ csp-hashes.mjs 构建后注入内联片段 sha256 到 CSP
│ ├─ check-redline.mjs 红线词扫描
│ └─ dist-hash.mjs 产物指纹
└─ .gitea/workflows/website.yml CI:构建+校验 → 同时发布主站与镜像
关键决策(务必理解)
1. 令牌同源(单一真相源)
颜色/字体/圆角/阴影等令牌的唯一真相源是仓库根 design/colors_and_type.css(铁律 1)。
scripts/build-tokens.mjs 在每次构建前把它读入并写出 src/styles/tokens.gen.css,
只移除其中加载 Google Fonts 的 @import url('https://fonts.googleapis.com/...') 一行
(任务硬性要求:不引第三方字体 CDN)。令牌数值一字未改,仍是单一来源、可随时再生。
若只拷贝了
web/website/子目录到干净环境(源文件缺失),生成器自动沿用已提交的tokens.gen.css,构建照常。
2. 字体全部自托管
原型用 Google Fonts CDN;本站改为 @fontsource/* 本地打包
(Sora / Manrope / Noto Sans SC / JetBrains Mono),woff2 随 npm ci 落地、
经构建产出到 dist/_astro/,由站点自身提供(font-src 'self')。
无任何第三方 CDN 请求 —— 性能 + 隐私 + 可达性。
3. 安全头(严格 CSP / HSTS)
public/_headers 下发严格 CSP:default-src 'self',script-src/style-src/font-src/
img-src/connect-src 全部收敛到 self(白名单仅 self + 自托管资源),外加 HSTS(含 preload)、
X-Content-Type-Options、X-Frame-Options: DENY、Referrer-Policy、Permissions-Policy。
Astro island 水合产生的少量内联 <script>/<style> 由 postbuild 的 csp-hashes.mjs
以 'sha256-...' 精确放行,不开 'unsafe-inline'。
curl 验证(部署后):
curl -sI https://<域名>/ | grep -iE 'content-security-policy|strict-transport-security'
4. 脱敏红线(铁律 13)
任何页面不得出现红线词(VPN/翻墙/科学上网/突破封锁/自由穿越/Go anywhere)。
scripts/check-redline.mjs 扫描 dist/**/*.html(含正文、属性、aria-label、meta),
例外白名单与仓库 ci/scan-redline.sh 一致(外部渠道 handle @PangolinVPN_bot/@pangolinvpn)。
CI 命中即失败。演示渠道/价格为占位,落地替换(见 design/CLAUDE.md §8)。
5. 无支付表单
定价区 CTA 一律「获取激活码 / 免费下载」,仅引导到外部渠道,页面内无任何支付表单(铁律 10)。
部署:Cloudflare Pages 主站 + ≥1 活跃镜像
主站与镜像各是一个 Cloudflare Pages 项目,部署同一份 dist/,绑定不同域名
(域名归属 #16 域名池体系)。镜像用于「主域名被墙」场景(doc/05 Runbook)。
手动部署(任一项目):
cd web/website
npm ci && npm run build
npx wrangler pages deploy dist --project-name <PAGES_PROJECT> --branch main
CI(.gitea/workflows/website.yml):push web/website/** → 构建(红线 + lint + CSP 哈希)
→ 同时 wrangler pages deploy 主站与镜像。需配 Secrets:CF_API_TOKEN / CF_ACCOUNT_ID /
CF_PAGES_PROJECT_MAIN / CF_PAGES_PROJECT_MIRROR,并把 runs-on 改成实际 runner label。
_headers(CSP/HSTS)由 Cloudflare Pages 原生支持;换其它静态托管时需用各自机制下发等价头。 主站与镜像构建时 SITE_URL 取同值,使 canonical 始终指向主站(避免 SEO 竞争), 且两边产物 hash 完全一致。
主站 ↔ 镜像内容一致性校验
产物为内容哈希、构建确定性,相同源码 → 相同 dist → 相同指纹:
npm run hash:dist # 主站与镜像两路构建后比对此值,应完全相同
灾备重放(RTO 秒级)
从任意干净环境(仅需 Node + 本目录)即可重建并部署到任意静态托管/新域名:
# 1) 取本目录(git 仓库即备份)
git clone <repo> && cd <repo>/web/website
# 2) 干净安装 + 构建(无网络外部依赖,除 npm registry)
npm ci
npm run build
# 3) 把 dist/ 整目录上传到任意静态托管 / 新 Pages 项目 / 对象存储+CDN,绑定新域名即可。
# 切换镜像/新域名只改「绑定」,无需改代码。
- 产物是纯相对路径静态文件,不依赖特定域名,可直接放到任意主机。
- 严格 CSP 全部
self,换域名无需调整。 _headers随dist/一并产出(Cloudflare Pages 自动识别)。
演练记录
- 2026-06-13:在本仓库 worktree 干净环境执行
npm ci && npm run build跑通,dist/产出 2 个页面(/、/en/)+ 自托管字体 +_headers;两次连续干净构建npm run hash:dist指纹一致(c1f6691e…cb65),验证可复现 / 主站镜像一致性。 上传dist/到新静态托管为纯文件拷贝,<10 分钟可完成(受限于上传带宽,无构建/配置阻塞)。