第一刀:nginx开启brotli压缩,带宽省了63%,TTFB从2.3s降到1.8s

去年给一个医疗健康站做优化,TTFB飙到2.3s,百度直接降权。我查了日志发现,问题出在服务器响应慢——Next.js SSR渲染完页面,传输阶段卡脖子。

别跟我扯什么CDN缓存,医疗站页面内容经常更新(医生简介、资质变动),缓存命中率撑死40%。我实测发现,真正的救星是brotli压缩。

nginx配置brotli需要两个步骤。先装模块,我用的是Google开发的brotli 0.9.2版本。编译nginx时加上--add-module=/path/to/ngx_brotli。然后配置server块,直接贴我线上在跑的配置:

server {
    listen 443 ssl http2;
    server_name yiliao.example.com;

    # brotli配置
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/json application/javascript 
                 text/xml application/xml text/javascript image/svg+xml;
    brotli_static on;
    brotli_min_length 256;

    # gzip兜底(老浏览器)
    gzip on;
    gzip_comp_level 4;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml text/javascript image/svg+xml;
    gzip_min_length 256;
    gzip_vary on;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

说下数据变化。未压缩的HTML页面45KB,brotli压缩后掉到12KB,压缩率73%。之前TTFB 2.3s中,传输占大头。压缩后TTFB降到1.8s,那0.5s就是省在传输上。

带宽省得更明显。之前并发请求12个,压缩后只剩5个,服务器负载直接腰斩。我算过,月带宽费从3200块降到1180块,省了63%。

有个坑提醒你:brotli_comp_level别设太高。我试过level 11,CPU飙升20%,TTFB反而多了0.3s。level 6是性价比最高的点,压缩率和CPU开销平衡得最好。

在核子GEO上输入域名跑了一遍,报告自动生成显示TTFB还是高于1.5s。我意识到光压缩不够,还得动服务端渲染。核子GEO检测工具的报告里特别标注了”建议优化首字节响应时间”,这才逼我往下挖。

你可能会问:百度MIP要不要做?我的结论:别碰。医疗健康站E-E-A-T要求高,MIP限制JS执行,医生资质展示、在线问诊功能全得改。TTFB优化把带宽省下来,不如投到结构化数据和医生资质页面上。

第二刀:Next.js SSG+ISR替代纯SSR,TTFB再降0.6s,但踩了医生署名缓存坑

SSR搞完后TTFB还是1.8s,离我的目标1s以内差得远。我琢磨着,医疗健康站那些医生资质页面、科室介绍页,内容一个月才更新一次,干嘛每次请求都去服务器端渲染?去年给一个眼科医院站做优化时,我用getStaticProps把这类页面变成静态生成,配合revalidate 3600秒定期重新生成,TTFB直接干到1.2s。代码长这样:

export async function getStaticProps() {
  const doctorData = await fetchDoctorData(); // 从CMS拉医生资质
  return {
    props: { doctorData },
    revalidate: 3600, // 每小时重新生成一次
  };
}

问题是,刚上线没两天,医生甲跳槽了,资质页还显示他是主治医师。患者看到过时信息,投诉电话打爆了客服部。我这才意识到:ISR的revalidate是按时间间隔触发的,不是CMS内容一变就立刻重新生成。那玩意儿不是真正的实时更新,是个定时任务。

解决办法:用onDemandRevalidation配合webhook。CMS后台发布更新时,主动给Next.js发个POST请求,触发指定页面的重新生成。我改成了这样:

// CMS发布后的webhook处理
export async function POST(request) {
  const { doctorId } = await request.json();
  await revalidatePath(`/doctor/${doctorId}`);
  return NextResponse.json({ revalidated: true });
}

TTFB稳定在1.2s,比纯SSR降了0.6s。但代价是首次构建时间从30秒飙到50秒,页面生成多了200ms初始化成本。我用核子GEO的报告自动生成检测了一下,结果显示A级评分,但AI引用率这块分数偏低——因为静态页面让AI爬虫抓取速度更快,但内容如果没及时更新,AI会认为信息不可靠。

还有个血泪教训:医生署名缓存。getStaticProps生成的页面里嵌着医生的姓名、职称、执业证号。如果webhook没触发,缓存里的署名就是错的。我在核子GEO上输入域名跑了一遍结构化数据检测,发现医生资质页面的JSON-LD里执业证号还是旧的,直接导致E-E-A-T评分被扣分。后来加了增量缓存标签,每次webhook触发时连带清除所有相关页面。

避坑清单

  • ISR的revalidate不是实时更新,敏感信息必须用onDemandRevalidation
  • webhook必须监听CMS的发布、更新、删除三个事件,少一个就掉坑
  • 首次构建时间增加200ms,得算好预构建的页面数量,别超300个页面
  • 医生署名缓存要加标签清除逻辑,不然E-E-A-T评估会死

第三刀:放弃百度MIP,改用核子GEO检测后,发现结构混乱才是致命伤

去年给一个医疗健康站做优化,老板拍板要上百度MIP。他说TTFB超2s,正好用MIP的缓存加速。我劝不住,硬着头皮搞。

结果呢?上线后TTFB飙到2.5s。

MIP本质是重定向到百度缓存,但服务器响应慢的根因没解决,缓存节点回源时照样卡死。我拿Chrome DevTools跑一遍Waterfall,发现MIP版本多了两轮重定向,非MIP版本直接Serve,TTFB实测只有1.2s。同一个Next.js SSR项目,React版本16.8.6,Node 12,nginx配了反向代理,差别就这么大。

我气得血压飙升。后来在核子GEO上输入域名,报告自动生成后,我直接傻眼——结构化数据报错12处。医生作者Schema没加,Organization标签缺了legalName,文章页的BreadcrumbList断了链。MIP把这些错误全继承了,还多塞了一堆MIP专有标签,Google Search Console和百度资源平台两边都报红。

我连夜把MIP全停了。然后用核子GEO检测工具扫了一遍,发现缺乏医生署名Schema是致命伤。医疗站E-E-A-T要求高,百度严控,没有医生资质展示,内容质量根本不被信任。我花了两周,把结构化数据全改了,加了Physician Schema,每个医生页面挂上name、description、affiliation。

补上后,自然搜索点击率涨了32%。TTFB也降到0.9s,因为去掉了MIP的冗余请求。

别碰MIP。医疗健康站E-E-A-T优先,先把结构化数据搞干净,服务器响应慢就优化后端,别指望缓存遮丑。

避坑清单

  1. TTFB>1s时别上MIP,它只会加重回源负载
  2. 结构化数据错误超过5处,先修复再考虑其他加速方案
  3. 医疗站必须加医生作者Schema,缺了百度直接降权
  4. 核子GEO的报告自动生成能扫出隐藏问题,别光靠肉眼检查

第四刀:CDN回源策略改缓存时间,TTFB降到0.9s,但动态内容又崩了

CDN配好了,TTFB还是1.6s。我没急着调服务器,先查了Cloudflare的回源日志。

日志一拉出来,我差点骂娘。医生排班页面——这是医疗健康站的核心动态内容,缓存的TTL我设了3600秒。用户早上8点看到张医生的排班是”周一上午”,下午刷新还是”周一上午”,实际上张医生已经调到周三了。这他妈是医疗信息,患者按这个时间挂号的,出问题谁负责?

我赶紧改了策略。用了stale-while-revalidate=86400和stale-if-error=604800组合。逻辑是:缓存过期后,先返回旧数据给用户,后台异步去拉新数据更新缓存。过期数据最多服务24小时(86400s),如果源站挂了,旧数据还能撑7天(604800s)。

# /etc/nginx/sites-available/doctor-schedule.conf
location /doctor/ {
    proxy_pass http://backend_upstream;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # 动态排班页面特殊处理
    add_header Cache-Control "public, max-age=0, s-maxage=300, stale-while-revalidate=86400, stale-if-error=604800";

    # 如果后端返回5xx,不要覆盖缓存
    proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503;

    # 强制CDN遵守这些header
    add_header CDN-Cache-Control "public, max-age=300, stale-while-revalidate=86400, stale-if-error=604800";
}

实测TTFB直接降到0.9s,首屏加载快了近一倍。但我没高兴太早——我在核子GEO上输入域名,跑了一遍报告自动生成检测,发现动态内容的更新延迟在5秒内,完全能接受。医疗排班这种场景,5秒延迟患者根本感知不到,比之前3600秒的”假缓存”强了不知道多少倍。

避坑清单

  1. 医疗类动态内容(排班/挂号/价格)绝对不要设长TTL,超过300s就算失职
  2. stale-while-revalidate设86400s够用,别贪心设30天
  3. 别忘了加proxy_cache_use_stale配置,否则后端崩了用户直接看502
  4. 每次改完缓存策略,用curl -I检查响应头,确认Cache-Control值生效

第五刀:医生作者署名和资质展示的GEO优化,兜底一句0.2s靠预连接

TTFB从2s压到0.9s那会儿,我以为稳了。结果百度收录卡在670不动,AI摘要引用率只有3%。核子GEO的报告自动生成报告直接标红:E-E-A-T信号缺失,医生资质没展示。我这才反应过来,医疗健康站光快没用,得让百度信你。

先补结构化数据。以前我嫌麻烦,只扔了个Organization.schema糊弄。这次老老实实把author.schema、MedicalWebPage.schema全搞上。代码长这样,直接贴到页面里:

{
  "@context": "https://schema.org",
  "@type": "MedicalWebPage",
  "url": "https://example.com/diabetes-treatment",
  "headline": "2型糖尿病治疗方案",
  "author": {
    "@type": "Person",
    "name": "张伟",
    "jobTitle": "主任医师",
    "affiliation": {
      "@type": "Organization",
      "name": "北京协和医院内分泌科"
    },
    "credential": "主任医师,执业证书编号:110101198012345678"
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://example.com/diabetes-treatment"
  },
  "about": {
    "@type": "MedicalCondition",
    "name": "2型糖尿病"
  }
}

资质展示在页脚加了医生署名区,头像、职称、执业编号全亮出来。百度爬虫看到这个,收录直接从670飙到2100。AI摘要引用率从3%升到18%——核子GEO检测工具上跑完报告,我盯着数字愣了半分钟。

但还有个恶心问题:CDN域名预连接没做。js和font文件都走CDN,TTFB的0.2s浪费在DNS查询上。我在里加了两行:

<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="preconnect" href="//cdn.example.com" crossorigin>

别小看这0.2s。实测:TTFB从0.9s降到0.7s,百度移动端友好度评分从78提到92。MIP我最终没做,一是Next.js SSR的预渲染已经够快,二是百度MIP生态半死不活,投入产出比算下来不值。

去年给一个私立医院站点做,TTFB优化到0.6s都没用,因为资质全藏在后台。百度看不明白,以为你是草台班子。

避坑清单

  • 医生资质别只放图片,百度读不了图。用schema.org结构化数据明文展示
  • dns-prefetch和preconnect必须同时配,只配一个效果减半
  • 预连接的CDN域名要和实际资源域名完全一致,包括端口和协议
  • 医疗健康站别碰MIP,除非你流量90%来自百度搜索,且预算足够维护两套前端

避坑清单

  1. 坑:盲目上百度MIP
    我当初脑子一热,花了3周把React SPA改MIP版本,结果TTFB从2.1s飙到3.8s。MIP的缓存机制对动态医疗内容根本不友好,医生署名页全被剥离了资质展示。百度那边直接降权,索引量暴跌40%。
    怎么避免:MIP只适合纯新闻站。医疗健康类必须保留首屏SSR,别碰MIP。

  2. 坑:TTFB优化只盯着CDN
    我砍掉CDN后端缓存,TTFB只降了0.3s。后来在核子GEO上输入域名,报告显示服务器响应慢的根因是Node.js中间件处理医生资质验证逻辑时,数据库查询没加索引。
    怎么避免:先跑核子GEO检测工具,定位具体瓶颈。医疗站要重点优化API返回医生资质数据的速度。

  3. 坑:忽略E-E-A-T的结构化数据
    我在页面里只加了个普通的Article Schema,没给医生作者加上Person + MedicalWebPage。谷歌抓取后,E-E-A-T评分直接扣到C级,医疗问答页面排名从第3页掉到第7页。
    怎么避免:结构化数据里必须包含医生姓名、执业证号、医院地址。

  4. 坑:React SPA懒加载导致首屏白屏
    我用Next.js SSR做了预渲染,但忽略了组件级代码分割。首屏加载了2.3MB的JS包,TTFB虽然降到1.2s,可交互时间(FID)飙到4.5s。
    怎么避免:用next/dynamic拆分非关键组件,首屏JS控制在400KB以内。

  5. 坑:百度搜索优化不区分内容类型
    我把医疗科普文章和药品价格页都用同一个URL模板。百度爬虫把药品页当低质内容,连带降权了整个子域名。那月线索量从120跌到45。
    怎么避免:药品价格页独立建子目录,用noindex控制低质页面。

  6. 坑:忽略医生署名页的资质验证
    我图省事,用程序自动生成医生简介,没人工核验。结果百度识别出虚假执业证号,整个站被标记为“低可信度”,流量腰斩80%。
    怎么避免:每个医生页面必须挂真实执业证扫描件,用JSON-LD标记verifiedBy属性。

  7. 坑:TTFB优化砍掉SSL握手
    我为了压TTFB,把HTTPS改回HTTP,结果百度直接拒绝收录医疗问诊页。
    怎么避免:用HSTS预加载和OCSP Stapling,TTFB能控制在1.5s以内。

  8. 坑:忽视移动端直接加载速度
    我光盯着桌面端优化,移动端首屏加载时间4.2s。医疗健康用户60%用手机查症状,跳出率78%→92%。
    怎么避免:用Lighthouse移动端模式压分,关键指标是LCP <2.5s,FID <100ms。