主题
WellChina 速度优化方案
适用范围: 生产域名
wellchina.top(Next.js 15.3 + Vercel Pro + Supabase Tokyo) 配套文档: 速度优化追踪(每次迭代的实测数据)· 全球可访问性与性能监控方案 月费: $20/月(Vercel Pro)
当前状态
Milan 4G iPhone 实测: TTFB 1.54s · LCP 2.43s · CLS 0。LCP 已跨进 Google CWV "Good"(< 2.5s)阈值。详细数据序列见 performance-tracking。
架构
┌────────────────────────────────────────────────────────────┐
│ 全球用户(NA / EU / 东亚 / SEA / 俄罗斯 等非中国大陆) │
└──────────────────────────┬─────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Vercel Edge Network(300+ POPs 全球) │
│ • HTML:匿名公共路由 s-maxage=300 + SWR=86400 缓存到 POP │
│ • Static JS/CSS:长缓存 + immutable │
│ • Static 图片:/public/(146 张:site-assets + city + │
│ hospital 图全本地化) │
│ 命中率目标 ≥ 95%,TTFB(命中)< 100 ms 全球 │
└──────────────────────────┬─────────────────────────────────┘
│ 仅缓存未命中(< 5% 流量)
▼
┌────────────────────────────────────────────────────────────┐
│ Vercel Pro Function(regions: ["hnd1"] Tokyo 单区域) │
│ • SSR 缓存未命中走这里 │
│ • 表单提交 / 写 API / Auth gate / Admin │
└──────────────────────────┬─────────────────────────────────┘
│ 同区域 ~5 ms RTT
▼
┌────────────────────────────────────────────────────────────┐
│ Supabase Tokyo(ap-northeast-1)— 单一状态源 │
│ • PostgreSQL + Auth + Realtime + Storage │
│ • 所有有状态的东西都在这里,零数据复制 │
└────────────────────────────────────────────────────────────┘
旁路(无区域绑定): DeepL API · Stripe · Resend · Cloudflare Email Routing为什么是这个架构
WellChina 是读密集应用——99% 流量是匿名浏览(医院/手术/价格/城市/指南),只有联系表单、聊天、注册、admin 是写。这种场景下边缘缓存效率最高,多区域复制是过度工程。
| 整洁维度 | 体现 |
|---|---|
| 单一主区域 | 所有 stateful 服务都在 Tokyo(function / DB / auth / realtime / storage) |
| 零数据复制 | 不用 read replica、不用多 region write、不用同步逻辑 |
| 单一 state 供应商 | Supabase 一家管 DB + Auth + Realtime + Storage |
| 明确两层缓存 | Vercel CDN(HTML / 静态图)+ Vercel image optimizer —— 没有缓存交叉 |
| 函数 region 选择有理论支撑 | hnd1 跟 Supabase 同 AWS region,function ↔ DB RTT 5 ms |
| 代码层不变 | 不需要"读副本路由"、"region-aware client"等胶水代码 |
已完成的工作
每项一行说明,详细量测数据见 performance-tracking。
| 改动 | 主要收益 | Commit |
|---|---|---|
vercel.json: { "regions": ["hnd1"] } — function 跟 Supabase 同 AWS region | TTFB -73%(function ↔ DB RTT 从 ~150ms 降到 ~5ms) | 2ce6e48 |
CLS 修复:28 个 @font-face 从 font-display: swap → optional | CLS 0.186 → 0(完美归零) | 4128c62 |
静态图本地化:146 张图(32 site-assets + 13 city + 101 hospital)从 Supabase Storage 搬到 /public/ | LCP -2.3s(hero/category/hospital 图全部本地化,零 cold-miss) | a92a03e |
Layout 不再读 cookies/headers:root layout 移除 getLocale();[locale]/layout 移除 auth.getSession() + getRegion();GeoAdvisoryBanner 改 client;首页 priceHighlights 改 client component | SSR 输出对所有访客一致——边缘缓存的前置条件 | c8d1fdf |
首页边缘缓存:next.config.ts 给 / + locale-prefixed `/zh | ja | ...加Cache-Control: public, s-maxage=300, stale-while-revalidate=86400;page.tsx加export const revalidate = 300` |
Build 加固:admin / account / sitemap 加 force-dynamic;prisma connection_limit 在 build phase 降到 2 避免打爆 Supabase pooler | build 稳定 | 562f3b9 |
/sitemap.xml 24h CDN 缓存 | sitemap runtime 生成 + CDN 缓存,build 不再查 DB | 562f3b9 |
后续可选优化(按 ROI 排序)
1. 扩展边缘缓存到其他公共页
ROI 最高:cache 模板已验证,加 next.config.ts source rule 即可。
目标路径:/hospitals、/procedures、/cities、/pricing、/compare、/guides、/search、/contact(及它们的 locale 前缀变体)。
前置检查:每页确认无 server-side cookies() / headers() / 用户专属逻辑。如果有(如基于 cookie 的 region price),按首页 priceHighlights 同样套路抽 client component。
预期:这些页 LCP 也跌到 1-2s 区间。
工时:~30 min 配置 + 每页 0~1 h client 化(取决于是否有 region 依赖)。
2. 静态化纯文案页
/about、/methodology、/privacy、/editorial-policy 这 4 页只渲染翻译文本,无 DB / 无 region。
ts
export const dynamic = 'force-static'
export const revalidate = 86400预期:build-time SSG,永远命中边缘 < 100 ms TTFB。
工时:30 min。
3. Hero 图 <link rel="preload">
让 hero 在 HTML parsing 阶段开始下载,不等 CSS。
tsx
// app/[locale]/page.tsx 的 <head> 注入
<link rel="preload" as="image" href="/site-assets/brand/hero-bg.webp" />预期:首页 LCP -200~400 ms。
工时:30 min。
4. WAF Rate Limiting(Pro 含)
/api/contact / /api/search / /api/chat/* 加 rate limiting 规则:
/api/contact: 5 请求 / 10 min / IP(防 spam)/api/search: 60 请求 / 1 min / IP(防刷)/api/chat/*: 30 请求 / 1 min / 已认证 user- 已知爬虫 + 不在 AI bot 白名单的 UA: deny
预期:节省 function CPU 配额 + 防滥用。
工时:1 h(在 Vercel Dashboard 配置)。
5. Speed Insights RUM 长保留 + 告警
Pro 含 30 天 RUM 保留。配置 Observability 仪表板:按 region 切片 p50/p95 LCP / INP / CLS,设异常告警。
预期:拿到真实用户分布数据,替代 lab 单次 trace 当长期门槛。
工时:30 min。
6. next-intl namespace 拆分
每个 locale 当前是单一 JSON(th.json 84 KB)。拆成 common.json / home.json / hospitals.json / chat.json / ...,每页只下它要的 namespace。
预期:每页 hydration JSON 减 50-70%;首屏 JS payload 减小。
工时:半天~一天(需要审查所有页面用了哪些 keys)。
7. 字体 Critical Subset + Preload
把首屏一定用到的 latin face(Lora 400 + Source Sans 3 400)拆 critical 内联 + preload,其他语种 face 保持异步。
注:当前
font-display: optional已经避免 CLS。这一步是"让首访用户也能看到 web font"——慢网下首访仍 fallback。如果品牌字体不是核心,可跳。
预期:首访用户 LCP 文本部分 -200~500 ms。
工时:1~2 h。
8. AnimatedSection CSS-only / 减少 framer-motion
useInView + motion.div 给每个 section 引入 React 状态 + 监听器。改用 IntersectionObserver + CSS class,零运行时依赖。
预期:framer-motion 不再进首屏 critical bundle,约 -30~60 KB Gzipped。
工时:2~3 h。
9. ISR revalidateTag for admin writes
Admin 改医院 / 手术 / 价格表后调 revalidateTag('cities') / revalidateTag('procedures'),让缓存按需失效,不等 5 min TTL。
预期:admin 写完立即在公共页生效。
工时:1~2 h。
考虑过但放弃的备选架构
多区域 function(Pro 支持最多 3 个)
regions: ["hnd1", "iad1", "fra1"] 看起来很美好但对我们是反向收益:
- 单 Supabase Tokyo + 多 function region = 非 Tokyo function 每次查询都要跨洋去 Tokyo DB
- Milan 用户路径:Milan → fra1 function(快)→ Tokyo DB(跨大陆 200ms)→ fra1 → Milan
- vs 单 hnd1 + edge cache:Milan → hnd1 edge cache 命中 → Milan,不需要 function 执行
要让多区域真正生效需要同时升 Supabase Pro + 加 3 个读副本($25 + ~$30/月,总月费 $75)+ application-level 读写分离代码。ROI 不划算。
Edge Runtime + Prisma Accelerate
- 月费 $20 + $29 = $49
- 收益:function 在每个 POP 执行(V8 isolate,零冷启动)
- 代价:Realtime / Auth / Storage 仍走 Tokyo;Node-only API 不可用
- 边缘缓存命中后已经不执行 function,Edge Runtime 优势不显著
Cloudflare CDN 反代 Supabase Storage
原方案是只搬 site-assets 到 /public/,hospital 图(101 张)继续走 Supabase 但用 CF CDN 反代加速。已被「全 146 张搬 /public/」替代:体验严格更优(零 cold-miss),架构更简单(少一个外部服务)。
Supabase 整体迁 us-east-1
如果不付 $20/月,把 Supabase 迁到 us-east-1 让它跟 Vercel Hobby 锁死的 iad1 function 同 region 也能消除跨洋瓶颈。作为零月费备选保留:迁移涉及 pg_cron / RLS / Storage / Realtime publications 重建,风险高于 hnd1 + Pro。
Vercel Pro 配额参考
以当前流量,月度 usage credit $20 完全覆盖 metered 项目:
| 项 | Pro 含量 | 超额价格 | 当前用量估计 |
|---|---|---|---|
| Fast Data Transfer | 1 TB | $0.15/GB | < 50 GB |
| Edge Requests | 10 M | $2/M | < 500 K |
| CPU Active Time | 40 hrs | $5/hr | 缓存命中后 ≪ 40h |
| Image Optimization | 5 K transformations | $0.05/1K | 第一波 ~10K(地理流量铺开后),后续低 |
| ISR Reads | 按用 | $0.40/M | 每 cache miss 一次 |
| ISR Writes | 按用 | $4/M | 每 revalidate 一次 |
| $20 月度 usage credit | $20 | 自动抵扣 | 小型站基本只交基础月费 |
默认开启 / 自动生效的能力
| 能力 | 状态 | 备注 |
|---|---|---|
| Fluid Compute Scale to One | 自动启用 | 99.37% 请求无冷启动;缓存未命中时 function 永远 warm |
| Skew Protection | 自动启用 | 部署期间旧客户端 JS 不会拿到新部署的 chunk URL |
| Concurrent Builds | 自动启用 | — |
| Image Optimization | 自动启用 | 监控月度 transformation 量,必要时调高 cache TTL |
| Speed Insights RUM | 部分启用 | 后续完整启用 + 设置告警(见可选优化 #5) |
| WAF Rate Limiting | 未启用 | 后续启用(见可选优化 #4) |
UX 已接受的取舍
为了启用边缘缓存(layout 移 client),首屏会有两个轻微 flash:
- Auth flash:首屏 ~50–200 ms 内 Navbar 显示「未登录」状态,client 端
getSession()跑完后切到登录态 - Region flash:首屏 region 默认
US,client 从 cookie 读到正确 region 后切;价格高亮(取决于 region)也会闪一下
回访用户 + 快网络感知不到。Milan 4G 首访用户的 200-400 ms flash 已通过用户测试确认可接受。如果将来产品决定不能容忍,备选方案是 Vercel Edge Config(边缘 KV,< 1ms 读)让 region 仍 server-side 解析。
验收门槛
每次合并优化 PR 后必须在 24h 内重测;如果出现以下情况之一,回滚 PR:
- 任一路径 LCP 比上一基线 +200ms 以上
- Lighthouse A11y / BP 降 ≥ 3 分
- WPT Milan 重测 LCP > 8s → 立即排查
- 任意页面
x-vercel-cache: HIT比例下降(说明 cache 被破坏)
每完成一个步骤,按同口径重测:WPT Milan iPhone 15 4G + Speed Insights RUM。数据填回 performance-tracking。
关键参考资料
- Vercel Functions Region 配置(hnd1)
- Configuring regions for Vercel Functions(多区域上限)
- Vercel Fluid Compute(冷启动消除原理)
- Scale to one: How Fluid solves cold starts
- Vercel Edge Network Caching
- Vercel WAF Rate Limiting
- Vercel Limits(配额详情)
- Vercel Pro 计划定价
- Image Optimization 配额与定价
- Vercel Edge Config
- Next.js Partial Prerendering