一个单页React应用,每秒都在崩溃边缘试探,最终扛住了每月5000万次访问。这不是魔法,是三个被大多数团队忽略的工程决策——而它们几乎零成本。
第一刀:ISR不是优化,是生存策略
我加入时,Quran.com还在用客户端渲染。每个用户打开章节页,都要等服务器现场拼HTML。中东和亚洲用户平均等3秒,很多人直接关掉。
迁移到Next.js的增量静态再生(ISR, Incremental Static Regeneration)后,我们在构建时预渲染全部章节页,每小时后台刷新一次。内容几乎不变的古兰经章节,变成了纯静态文件。
首字节时间(TTFB, Time to First Byte)在亚洲和中东下降超过70%。不是优化,是直接把用户体验从"加载中"拽进了"已呈现"。
但ISR有个陷阱:默认配置下,CDN不会乖乖配合。很多团队到这里就停了,以为"用了Next.js就够了"。
第二刀:CDN缓存的隐藏语法
我们在Vercel前加了Cloudflare,不是为了冗余,是为了劫持缓存逻辑。关键在这行配置:
Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400
翻译成人话:用户永远拿到缓存版本,哪怕它"过期"了——后台悄悄去验证更新,用户无感知。利雅得、卡拉奇、雅加达的用户,现在从最近的节点取页面,而不是等美国服务器响应。
CDN缓存命中率从约40%飙升到92%。剩下8%才是真的需要回源的新请求。
这个stale-while-revalidate头部,被无数Next.js教程跳过。它不是什么新特性,2017年就进了RFC,但 production 里见过的人不多。原因很讽刺:本地开发测不出来,必须上真流量才能看见效果。
第三刀:对依赖的"审计式"残忍
5000万用户意味着任何1KB的浪费都会被放大。我们逐行审查node_modules,发现三个"体积刺客":一个图表库只用了10%的API,却拖进全部D3;一个日期处理库和原生Intl.DateTimeFormat功能重叠;还有个工具函数库,我们自己重写只需要200行。
配合激进的代码分割(Code Splitting),把路由级组件拆成独立chunk。
初始JS包体积缩减约45%。在2G网络为主的南亚市场,这意味着"能打开"和"直接放弃"的区别。
有个细节:我们没动业务代码,全是基础设施层的手术。很多团队一上来就重写组件,其实是抓错了优先级。
被误读的"过早优化"
这三件事做完,团队内部有过争论。有人觉得"我们还没到那个规模",ISR和边缘缓存是"过早优化"。
我的回应很直接:等你有规模问题时,你已经没空重构了。5000万用户下的100毫秒优化,每天影响数百万次访问。这不是性能竞赛,是留存率的复利计算。
现在回头看,那些"看起来太早"的决策,恰恰是后来不用凌晨救火的原因。
如果你在用Next.js做内容型产品,ISR和边缘缓存不是可选项。构建时就埋进去,比事后拆弹便宜一百倍。
最后留个数据:迁移完成后,我们在巴基斯坦的用户次日留存提升了多少?原文没提,但作者说"更快加载意味着更多回访"——这个因果链条,你的团队敢押注吗?
热门跟贴