Last-Modified 响应头缺失:3 个原因 + 修复路径

页面更新后 Google 一直没重抓——服务器没返回 Last-Modified / ETag。

你更新了一篇文章。过了几天。Google 缓存版本还显示旧内容。JSON-LD 的 dateModified 是对的、页面本身渲染的是新内容——但 Google 没重抓。一个常见原因:服务器没返回 Last-Modified HTTP 头(或所有请求都返回同样的值、不随内容变)。没有 Last-Modified,Google 没法判断页面自上次抓取后有没有变,重抓优先级就会被压低。

这在静态站上最常见:Vercel 和 Netlify 通常会自动设;Firebase Hosting 和一些自托管 nginx 不会。

常见原因

按命中率从高到低。

1. 托管平台不设 Last-Modified

Firebase Hosting、GitHub Pages、某些定制 nginx 配置不会自动给静态文件设 Last-Modified

怎么判断

curl -sI "https://yoursite.com/article" | grep -i last-modified

为空 = 没设。

2. CDN 把头剥了

源站返回 Last-Modified,Cloudflare 或别的 CDN 为缓存原因剥掉。

怎么判断:跑两次 curl -I——一次直连源站(绕 CDN),一次正常。源站有 Last-Modified、CDN 后没有,就是 CDN 剥了。

3. Last-Modified 一直是同一个值(部署时间)

某些静态站生成器把 Last-Modified 设为构建时间,所有页面都一样。Google 看不到差异就当无信号。

怎么判断curl -I 不同页面。共享同一个 Last-Modified 就是 bug。

4. Cache-Control: no-store 覆盖

如果设了 Cache-Control: no-storeLast-Modified 就没意义了——客户端不缓存。Google 也会把信号打折。

怎么判断

curl -sI "https://yoursite.com/article" | grep -E 'cache-control|last-modified'

cache-control: no-storelast-modified 都在就是冲突。

5. 只用 ETag 但漏 Last-Modified

ETag 没问题但 Google 对 HTML 页更偏好 Last-Modified。只发 ETag 不全。

怎么判断:头里只有 ETagLast-Modified。两个都加。

6. 页面由 worker / SSR 函数生成、没设头

Cloudflare Worker 或 Vercel Edge function 生成的页默认不加 Last-Modified

怎么判断:页面是动态生成的(看 cf-rayserver 头)且 Last-Modified 缺失。

最短修复路径

第 1 步:确认头缺失

curl -sI "https://yoursite.com/article" | head -10

Last-Modified 行。没有就需要加。

第 2 步:按平台启用

Vercel — 静态文件默认有。SSR 路由的响应里加:

return new Response(html, {
  headers: { 'Last-Modified': new Date(article.modifiedAt).toUTCString() },
});

Netlify — 默认有。_headers 文件自定义:

/article/*
  Last-Modified: ...

(注意:_headers 不支持动态值;要动态用 Netlify Functions。)

Firebase Hostingfirebase.json 显式加:

{
  "hosting": {
    "headers": [{
      "source": "**/*.html",
      "headers": [
        { "key": "Last-Modified", "value": "Wed, 21 Oct 2025 07:28:00 GMT" }
      ]
    }]
  }
}

Firebase 静态不能动态计算;可以用 build 脚本给每次部署设值。

GitHub Pages — 不支持自定义头。Last-Modified 很重要就别用它。

Cloudflare Workers — 手动加到 Response:

return new Response(body, {
  headers: {
    'Last-Modified': lastModifiedDate.toUTCString(),
    'Cache-Control': 'public, max-age=3600',
  },
});

第 3 步:值要反映真实内容变化

每篇:

const lastModified = new Date(article.modifiedAt || article.publishedAt).toUTCString();

不要所有页都用构建时间。

第 4 步:CDN 别剥

Cloudflare → Caching → Configuration → 确保 Last-Modified 不在任何 transform 规则里。默认是保留的。

第 5 步:源站和 CDN 都验证

# 源站(用源站 IP 或绕 CDN URL)
curl -sI "https://origin.yoursite.com/article" | grep -i last-modified

# CDN 服务的
curl -sI "https://yoursite.com/article" | grep -i last-modified

两个都应有 Last-Modified。源站有、CDN 没有就是 CDN 在剥。

第 6 步:用 If-Modified-Since 测缓存行为

LM=$(curl -sI "https://yoursite.com/article" | grep -i last-modified | cut -d' ' -f2-)
curl -sI -H "If-Modified-Since: $LM" "https://yoursite.com/article"

页面没变就应返回 304 Not Modified。返回 200 说明缓存没尊重头。

预防建议

  • 每次部署后立刻 curl -I 三个代表页面,确认 Last-Modified 存在且每页不同。
  • Last-Modified 从文章的真实 modifiedAt 字段设,不要从构建时间设。
  • CDN 配置里不要剥头。
  • Last-ModifiedETag 一起(最佳实践)。
  • CDN 服务的 HTML,加缓存规则让它尊重 Last-ModifiedIf-Modified-Since

相关阅读

标签: #SEO #排查