Hotdry.
web-architecture

个人站从 Cloudflare Pages 挪到阿里云 ESA:Next.js SSG/ISR 国内延迟更稳的实测记录

一个 0 预算的个人站,把 Next.js SSG/ISR 从 Cloudflare Pages 挪到 ESA,国内 TTFB 从 400-500ms 掉到 20ms 左右。纯踩坑记录,所有资源都靠免费额度和活动流量包。

本文同时参与两场活动:
1)「玩透 ESA」征文:https://event.alibabacloud-esa.com/
2)「发帖送 ESA」免费领取活动(需带官方链接):http://s.tb.cn/e6.0Fu67m

我的项目场景

  • 技术栈:Next.js 14(App Router)、SSG + ISR、Tailwind CSS。
  • 业务形态:知识付费小站,月 PV 9 万,带宽 45GB / 月。
  • 访问分布:国内 72%,港澳台 10%,北美 / 欧洲 18%。
  • 多媒体:每篇文章 6-10 张 WebP 配图,单图 200-400KB。

去年把前端放在 Cloudflare Pages,配合 Cloudflare Images 做裁剪和 WebP 输出,海外体验不错。但国内用户一直反馈 “首屏空白 2-3 秒”,最近双十一 PV 突增,问题更明显。预算为 0,只能优先折腾成本最低的方案。

为什么最初用 Cloudflare Pages

  1. 免服务器 + 自动 SSL
    接 GitHub,一键构建,Pages + Images 打包提供 HTTPS、缓存、基础防护,几乎零运维。

  2. Workers 边缘函数
    我用 Workers 做 A/B Test 和灰度,写法跟 Cloudflare 平台绑定,体验确实顺滑。

  3. 海外性能很强
    在旧金山 / 法兰克福测 TTFB 都在 40-60ms,图片裁剪也很稳。

问题暴露:国内延迟和图片回源

1. 国内 TTFB 不稳定

10 月用站长工具全站测速(平均值):

  • 北京:TTFB 430ms
  • 上海:TTFB 480ms
  • 杭州:TTFB 410ms
  • 广州:TTFB 470ms
  • 成都:TTFB 520ms

高峰期甚至飙到 700-900ms。原因大家懂:Cloudflare Pages 主节点在海外,国内没有边缘落地。

2. 图片偶发 522/523

Cloudflare Images 在香港节点回源,偶尔打满,日志里一天能看到 30-50 次 522/523,导致页面出现断图或等待 1-2 秒才恢复。

3. 成本不算低

  • Pages 免费,但 Images 按量计费 + 存储,月均 28 美元。
  • Workers KV 做 ISR 缓存控制又是单独计费。

流量一涨就开始心疼。

选择 ESA 的理由(站在个人开发者角度)

  1. 国内节点 + ICP 备案域名
    我的域名本来就在阿里云备案,ESA 能直接走国内边缘节点,理论上 TTFB 可以压到 20ms 以内。

  2. 边缘计算兼容 Node Runtime
    Next.js Middleware 可以直接跑在 ESA Edge Functions,上下文和 Node API 兼容度够用。

  3. 内置图片处理(图像缩放 + WebP/AVIF)
    避免再接第三方图片服务,减少 522/523 风险。

  4. 能 “白嫖” 起步
    我用的是 ESA 基础版 + 活动送的 50GB 流量包,目前 0 付费运行。超量单价也比我之前的 Images 便宜一些,但核心是先不花钱。

迁移步骤(实操记录)

第 1 步:打包构建产物

在 CI 里保持原来的 next build,输出 .next。为了让 ESA Edge Functions 处理 SSR/ISR,需要导出 Server Bundle:

NEXT_TELEMETRY_DISABLED=1 \
NEXT_PRIVATE_STANDALONE=true \
next build

生成的 standalone/ 目录直接上传到 ESA。

第 2 步:配置 ESA Edge Functions

  • 运行时选 Node.js 18,超时 3s。
  • 入口文件:standalone/server.js
  • 环境变量:NODE_OPTIONS=--enable-source-maps,方便排错。

第 3 步:静态资源分层缓存

  • /static/*/_next/static/* 缓存 30 天,cache-control: public, immutable.
  • 页面级://post/* 设置 s-maxage=300, stale-while-revalidate=86400,让 ISR 命中边缘缓存。

第 4 步:开启 ESA 图片处理

  • 上传原图到 OSS,ESA 开启 “边缘图片处理”,规则:
    • ?x-oss-process=image/resize,w_1280/format,webp/quality,Q_75
    • 移动端判断 UA,小于 768px 输出 w_720
  • 页面里统一调用:
const src = `${cdn}/images/${key}?x-oss-process=image/resize,w_${isMobile?720:1280}/format,webp/quality,Q_75`;

第 5 步:灰度与 A/B Test 迁移

  • 把原先 Workers 中的 AB 逻辑改成 ESA 边缘函数中间件,使用 Cookie 划分(逻辑保持最小改动):
export default async function handler(req, res) {
  const group = req.cookies.ab || (Math.random() > 0.5 ? 'A' : 'B');
  res.setHeader('Set-Cookie', `ab=${group}; Path=/; Max-Age=2592000; Secure; HttpOnly`);
  // 根据 group 分支到不同渲染路径
}

第 6 步:全链路监控

  • 前端埋点:TTFB、FCP、LCP、CLS、Error。
  • ESA 控制台打开访问日志 + 边缘函数日志,便于对比。

性能对比数据

12 月 1 日晚高峰(19:00-22:00)对比,取 5 地各 30 次样本(自测,样本量有限,仅供参考):

城市 Pages TTFB (ms) ESA TTFB (ms) 首屏时间 FCP (s)
北京 450 18 0.9
上海 470 19 0.9
广州 430 21 1.0
成都 520 25 1.1
深圳 480 18 0.9
  • TTFB 平均从 470ms 降到 20.2ms。
  • 首屏 FCP 从 2.6s 降到 0.96s,跳出率一周内从 39% 降到 24%(数据来自站内埋点)。
  • 图片错误率从每天 30+ 次 522/523 降到 0(统计 7 天)。

ESA 国内多地访问测速示意

成本和白嫖状态

  • Cloudflare 阶段:Pages 免费,但 Images + KV + 日志约 28 美元 / 月。
  • 迁到 ESA:目前用活动送的 50GB 流量包 + 基础版试用,实际扣费为 0。如果后续超量,按官方单价估算大概每月 < 20 美元,对我这个体量可以接受。

遇到的小坑与解决

  • Next.js 动态路由 rewrite:需要在 ESA 配置里显式把 /post/* 指向边缘函数,否则会被当作静态文件 404。
  • ISR 缓存同步:初次渲染后的 revalidate 可能在少数节点不一致,在 ESA 后台打开 “回源一致性” 后,缓存刷新变得稳定。
  • 源站 301:把原 Cloudflare 域名做 301 指向 ESA CDN 域名,否则部分用户的老书签会命中旧节点。

迁移总结(主观感受)

  • 如果主要流量在国内,Cloudflare Pages/Images 的海外优势难以兑现,延迟就是硬伤。
  • ESA 的国内节点 + 图片处理 + 边缘函数,让我基本不改业务代码就把延迟拉下来了。
  • 成本方面,现阶段靠活动流量包白嫖,后续超量再看;至少对个人站 / 小流量站是个可行的低成本选项。

接下来我计划:

  • 在 ESA 上打开 WAF 规则,观察误杀率。
  • 引入 stale-if-error,提升发布时的容错。
  • 继续写一篇 “ESA + Server Actions” 的小实验(顺便交征文任务)。
查看归档