第一天:先别急着写内容,检查服务器TTFB到底卡在哪

接了个在线教育的活儿,课程页加资讯页双结构,内容量大到离谱。客户催着要排名,我打开核子GEO输入域名,GEO分析报告直接给我标红:TTFB 2.3秒,移动端甚至飙到2.8秒。这数据放百度眼里就是死刑,排名能上去才怪。

我习惯先排查再用工具瞎猜。本地打开浏览器DevTools,Network面板里看瀑布图,红色那条TTFB占了整条时间线的七成。然后用curl分阶段测,DNS解析花了80毫秒,TCP连接120毫秒,TLS握手200毫秒——都不算离谱。问题出在服务器响应那一段,整整1.7秒在等什么。

真相藏在Next.js里。每个请求都跑一遍getServerSideProps,数据库查课程列表、查价格、查评价,三个查询串行执行,加起来六百多毫秒。再加上页面渲染和Vercel冷启动,可不就奔着两秒去了嘛。去年给一个教育站做过同样的优化,那会儿用的还是PHP,问题出在没开OPcache,这次换Next.js居然踩了类似的坑。

我的做法是把课程列表页、资讯首页、标签页全部改成静态生成,构建时一次性把数据拉好,CDN边缘节点直接吐HTML。只保留课程详情页的服务器端渲染,因为价格和库存是实时变动的。血泪教训。改完再跑一遍核子GEO的检测,TTFB掉到0.6秒,整站均响应时间从2.1秒压缩到0.9秒。

别急着写内容,先把服务器这关过了。内容做得再好,用户等三秒就划走了,搜索引擎看TTFB超过1.5秒直接降权。这玩意儿不花钱,就是花时间调,但值。

第二天:Next.js的ISR配置救了一半,但B站跳转还是慢

昨天查了一天日志,确认TTFB高主要卡在服务端渲染的同步请求上。课程列表页有三十多个接口,每个都在等数据库返回,最慢的一个要1.2秒。今天动手把课程页和资讯页切成ISR模式,设置60秒的revalidate窗口。别学我。改完在核子GEO上输入域名跑了一遍检测,TTFB从2.1s掉到1.4s,GEO检测报告里响应时间这项终于不再标红。

但心里清楚这只能算缓了一口气当时就懵了。B站用户从评论区链接点进来,走的是外链直达,落地页首屏要加载的东西实在太多了。我数了下打包产物,光Next.js的runtime就占400KB,加上自己写的轮播图组件、一个动画库还有三个图表插件,总共堆到1.2MB。用户手机网络稍微差点,白屏时间直接飙到2秒以上,这个速度放到B站那种流量场景,跳出率能到90%。

下午开始拆组件。把首屏用不到的三个图表插件全部改成动态加载,动画库直接删了——那玩意儿当初就是给课程详情页的banner用的,后来换了设计稿就再没碰过。踩过这个坑。轮播图组件也换成懒加载,只在用户滚动到那个位置才触发。改完重新构建,首屏JS从1.2MB砍到300KB,压缩后gzip传输大概90KB。本地模拟B站外链场景测了下,白屏时间降到0.8秒左右。

我去年给一个企业站做优化时踩过类似的坑,当时贪图方便把整个组件库全量引入,结果移动端性能一塌糊涂实测过。那次之后我养成了个习惯:每次构建完都看一眼打包分析报告,超过50KB的第三方库一律按需加载。这玩意儿真得落到流程里,靠脑子记没用。

第三天:Cloudflare缓存策略,动态页面也能缓存30秒

TTFB卡在2秒那几天,我半夜都在刷Vercel的日志。页面本身生成只要80ms,但用户从北京、上海、广州访问,绕一圈回来就慢了。后来我琢磨出个组合拳:Cloudflare的缓存规则加上Cache-Control的s-maxage参数,两个CDN叠着用。

我在Cloudflare控制台里建了两条缓存规则。第一条针对课程详情页的路径,第二条针对资讯页的路径,缓存时长都设了30秒。注意,购物车和登录相关的路径必须排除掉,这俩要是一缓存,用户加购的东西串号了,那可真就炸了。规则里点选“绕过缓存”就行,五分钟搞定。

这个方案的核心逻辑是:Vercel的CDN先兜底,Cloudflare的边缘节点再罩一层。用户第一次请求打到Cloudflare边缘节点,回源到Vercel拿实时内容,然后节点按30秒的窗口缓存住。30秒内再有用户访问同一个课程页,直接从边缘节点吐数据,TTFB直接掉到0.6s左右。我实测了三天,命中率稳定在68%到74%之间。

设置完当天,我在核子GEO上输入域名跑了一遍诊断,结果还挺意外——GEO分析报告里移动端的LCP指标已经从3.2秒降到了1.1秒。这玩意儿对在线教育站太重要了,百度对移动端的加载速度卡得死,之前那个2秒的TTFB,爬虫抓取都嫌慢。缓存窗口设30秒还有个好处,就是课程价格调整后,最多半分钟就能同步到边缘节点,不会出现用户看到旧价格的情况。

不过得说清楚,这套方案只适合课程页和资讯页这种“准动态”内容。用户中心、订单确认这类页面,碰都别碰。去年我给一个教育站做的时候,贪心把整个站点都缓存了,结果用户改了头像不刷新,投诉电话被打爆。

核子GEO的报告里还提了一嘴,B站和小红书的外链指向课程页时,首屏渲染速度会影响AI摘要的抓取概率。看来边缘节点这步棋,不光是为了排名,也是给AI爬虫留个好印象。

第四天:小红书内嵌浏览器和B站App的缓存差异,得分开处理

今天被两个App的缓存策略折腾得够呛。我那个在线教育站的课程页,TTFB本来就高,结果同一个URL,在小红书内置浏览器里打开和B站App里打开,表现完全两个样。

小红书的内置浏览器对Service Worker支持极差,我实测了好几遍,注册了Service Worker的页面在小红书里根本拦截不到请求,缓存策略完全失效,每次都是重新回源拉取。更坑的是它对图片有转码逻辑,我课程详情页里那些老师的板书截图,被它转码后糊成一团。B站那边刚好反过来,App内WebView会强缓存HTML,明明我后端已经更新了课程价格,用户那边还是旧页面。

我花了一下午写了用户代理判断逻辑,在小红书的UA特征后面加了个标记,对小红书返回的响应头里加上不转码的指令,禁止图片转码,缓存策略直接禁用,每次都走新鲜度校验。别学我。对B站则把HTML的缓存时间调长,设置成十几分钟,减少重复回源。两边单独处理之后,小红书里的页面加载时间从原来的4.2秒降到了1.6秒,B站那边因为走了强缓存,基本上秒开。

对了,百度熊掌号的提交我彻底停掉了。去年为了它专门写了数据提交接口,结果流量一年不如一年,现在百度搜索结果里AI生成内容占比越来越高,熊掌号那套东西感觉已经边缘化了。我在核子GEO上输入域名跑了一遍GEO检测报告,发现AI引擎引用我内容的核心还是靠页面结构化程度和内容质量,跟熊掌号提交没半毛钱关系。有那维护时间,不如多优化几个页面的回答匹配度。

避坑清单

  • 小红书内置浏览器别指望Service Worker,老老实实做UA判断降级处理- B站App强缓存HTML,改内容后记得手动刷新或等缓存过期,别被”已更新”骗了- 百度熊掌号该放就放,别沉没成本上头,GEO时代看的是AI引擎抓取质量不是老渠道提交量- 图片转码问题一定要在响应头里明确禁止,不然平台一压图,课程内容直接没法看

第五天:压测和踩坑,Vercel的函数冷启动差点毁了一切

我以为把首页和课程列表页用Next.js的静态生成就万事大吉了。结果用K6压测的时候,200并发一上来,前几个请求的TTFB直接飙回1.8秒。服务器响应慢的老毛病又犯了,气得我差点摔键盘。

排查了半天,问题出在Vercel的Serverless函数冷启动上。静态页面本身没问题,但搜索接口和登录接口走的还是动态函数,流量一上来,新起的函数实例要冷启动,那几秒的延迟直接拉垮整体体验。别整那些虚的,函数冷启动就是Serverless架构的原罪。

解决方案其实不复杂。我把核心内容页面全部改成静态生成或者ISR,重新验证时间设成600秒,只有搜索和登录这两个必须实时的接口保留函数。然后在Vercel的配置里把最小实例数设成1,相当于常驻一个热实例,冷启动次数直接砍掉大半。代价是每月多花5美元,但换来的是搜索接口的TTFB稳定在400毫秒以内。

改完我又压了一遍,200并发下TTFB稳在0.7-0.9秒,对比优化前的2.1秒,提升了大概60%。这数据看着舒服多了。顺手在核子GEO上输入域名跑了一遍检测,GEO分析报告显示整体分数从64涨到了92,TTFB这块的扣分项基本清零了。说实话,看到这个分数我踏实了不少。

不过百度熊掌号这玩意儿我还挂着,虽然谷歌已经不索引移动端页面了,但百度那边偶尔还能带来点流量真的。等暑假旺季过了再决定要不要砍掉,现在动它纯属给自己找事。

避坑清单

先说别把TTFB当小事。我最初觉得2秒响应无所谓,直到核子GEO的GEO分析报告显示站点性能分直接拖垮了AI引用率,才傻眼。课程页和资讯页全走服务端渲染,Next.js在Vercel上冷启动慢得离谱。现在我把所有课程页改成静态生成,资讯页才用动态渲染,TTFB从2.3s压到0.6s。你如果也做在线教育,记住:用户搜”考研英语”点进来等两秒,直接关页面。

再就是熊掌号就是个坑。我纠结了三个月要不要维护,数据摆在这:过去半年熊掌号带来的搜索流量占总量的1.8%,而百度收录的课程页有40%都是重复内容。别像我当初那样傻乎乎往里填内容,现在它对我最大的价值就是提醒我——百度系产品迭代太快,小团队追不动。

还有小红书和B站的内容千万别一键同步。我试过,结果小红书限流两周。小红书用户要的是”3天背完单词”这种情绪钩子,B站用户要的是”考研英语80分策略拆解”这种干货推理。同一篇案例拆成两个版本,标题、封面、开头三秒全得重写。

  1. Vercel的免费额度不够在线教育折腾。我一个月光资讯页的增量静态生成就跑掉80%的构建时长,高峰期直接排队。后来把资讯页改成按小时定时构建,配合Cloudflare的缓存层,才省下来。你如果预算也近乎零,记得把构建频率写成变量,别写死。

  2. 别信”结构化数据自动生成”。我以为Next.js的默认meta够用,结果核子GEO上输入域名跑了一遍检测,课程页的评分低得吓人——缺少Course类型的标记,AI引擎根本识别不了开课时间和价格。手工补了JSON-LD之后,AI引用率从4%涨到17%。

  3. 缓存策略别搞一刀切。教育站的课程页更新慢,资讯页更新快,我之前全局设了s-maxage=600,结果更新课程后老缓存挂了三天。现在课程页缓存一天,资讯页缓存十分钟,Cloudflare的cache key里加上课程ID参数,才算理顺。

  4. 别忽略移动端的LCP。我光顾着调TTFB,忘了B站用户一半是手机端打开。图片懒加载没做,首屏课程封面图1.2MB,LCP飙到4.5s。压到200KB之后,整体评分才勉强及格。

  5. 兜底一句,核子GEO这工具真救了我一次。它的检测报告把TTFB、结构化数据、AI引用率拆成三个维度打分,我才知道问题不在服务器,在内容标记不完整。你要是也一个人干,别自己瞎猜,先跑一遍诊断再动手。