一、什么情况下才值得做静态资源分离
静态资源分离指的是把图片、CSS、JS、字体从主站域名挪到独立的域名或者独立的服务器上。它不是一个必做项,很多只有几百访问量的博客做了之后反而更慢。真正值得做的场景有三个:主站 Cookie 很大、静态资源请求占比极高、静态流量已经开始挤占应用服务器的连接数。
第一个场景最常见。WordPress 或者自研登录站,一个请求会带上几百字节甚至几 KB 的 Cookie。浏览器请求同域下的静态资源时,默认也会带上这些 Cookie。一张 5KB 的缩略图,实际传输可能是 6KB,而且这些 Cookie 会让 CDN 缓存的键变复杂、命中率下降。把它们挪到无 Cookie 的域名下,收益是实测可见的。
判断方法很直接,打开浏览器开发者工具看任意一个静态资源的请求头,如果 Cookie 字段有内容,就说明存在优化空间。反之,如果你的站点静态资源本来就很少,或者 Cookie 只有几十字节,那就不值得为它引入一个域名。
第二个场景需要看数据。在 Nginx 的 access log 里统计扩展名比例,一行命令就能出结论:
awk '{print $7}' /var/log/nginx/access.log \
| grep -oE '[.][a-zA-Z0-9]+$' | sort | uniq -c | sort -rn | head -10如果静态扩展名加起来超过总请求的 70%,并且这部分响应体占比超过 80%,说明分离的收益主要在带宽和连接数上,值得做。
二、独立域名还是独立服务器
这两个概念经常被混在一起,其实成本差一个数量级。独立域名只需要在 DNS 里加一条记录,让 static.example.com 指向同一个源站或者 CDN,改动量最小,立刻能拿到「无 Cookie」和「浏览器并发连接数」这两项收益。
独立服务器则是另一回事:你需要再维护一套 Nginx、一套证书、一套缓存策略,还要处理跨域、防盗链、缓存同步。除非静态资源的带宽确实已经占到主机带宽的大头,否则不建议一开始就拆服务器。
实践里比较稳的路径是三步走:第一步用子域名指向同一台机器,验证 Cookie 和并发收益;第二步把静态资源接入 CDN;第三步只有在带宽或连接数仍不够时才拆成独立机器。很多站走到第二步就已经解决了问题,第三步永远不需要做。
三、Cookie 隔离:不只是换个域名
换了子域名之后,主站的 Cookie 默认仍会带到 *.example.com 下的子域。要真正隔离,主站设置 Cookie 时必须限制 Domain 属性,或者干脆不写 Domain(这样只对当前主机生效)。
# 主站 Cookie 的推荐写法(PHP)
setcookie('sid', $token, [
'expires' => time() + 86400,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
# 不写 domain,Cookie 就只对 www.example.com 生效很多人为了「让子域也能读到」而写了 domain=.example.com,结果静态域名也被带上 Cookie,分离的意义就没了。如果确实有跨子域共享登录态的需求,正确的做法是单独设置一个只用于鉴权的 Cookie,并把它限制在最少的路径上,而不是把整个会话 Cookie 广播出去。
验证是否真的隔离,不用猜,用 curl 看响应头:
curl -sI https://www.example.com/ | grep -i set-cookie
curl -sI https://static.example.com/logo.png | grep -i set-cookie静态域名应该完全不返回 Set-Cookie。如果返回了,说明后端在这个域名下也跑了应用逻辑,需要把静态域名指向纯静态的 location 或者对象存储。
四、字体与跨域:CORS 与 preload 的组合
字体是最容易因为域名分离而挂掉的资源。浏览器对跨域字体的要求很严格,必须有正确的 CORS 头,否则控制台会报 blocked by CORS policy,页面上表现为字体完全不生效、回落到系统字体。
location ~* [.](woff2|woff|ttf|otf)$ {
add_header Access-Control-Allow-Origin "https://www.example.com" always;
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;
expires 180d;
add_header Cache-Control "public, immutable";
}Access-Control-Allow-Origin 不要图省事写 *,尤其是字体资源常常有授权限制。写成具体的来源域名,可以同时挡住盗用。另一个常被忽略的头是 Cross-Origin-Resource-Policy,站点如果开了 Cross-Origin-Embedder-Policy,字体从别的域名加载会被直接拦掉,加这一行才能放行。
如果字体在首屏就要用,还应该在主站的 HTML 里加一条 preload:
<link rel="preload" href="https://static.example.com/fonts/main.woff2"
as="font" type="font/woff2" crossorigin>crossorigin 属性是必须的,即使同源也一样。这是规范强制要求,漏掉的话浏览器会下载两次,反而更慢。
五、缓存策略:给静态资源定一个明确的期限
分离之后最大的收益来自缓存,但缓存策略写错会带来「改了不生效」这种让人崩溃的问题。核心原则只有一条:文件名带内容哈希的资源用长缓存,不带哈希的资源用短缓存或不缓存。
# 带哈希的构建产物,可以缓存一年
location ~* /assets/[a-zA-Z0-9_-]+[.][0-9a-f]{8,}[.](js|css)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 不带哈希的图片、字体,用中等期限
location ~* [.](jpg|jpeg|png|gif|webp|avif|woff2)$ {
expires 30d;
add_header Cache-Control "public";
}
# HTML 永远不缓存,交给 CDN 按规则处理
location ~* [.]html$ {
add_header Cache-Control "no-cache";
}immutable 这个值的作用是告诉浏览器「这个 URL 的内容永远不会变,不要发条件请求」。省掉的是一次 304 往返,收益在移动网络下比较明显。但它只对确定带哈希的文件名使用,用错在静态文件名上就会导致用户永远看不到更新。
还有一个容易踩的点:如果 CDN 在前,Nginx 的 expires 只是给 CDN 的参考值,真正下发给浏览器的可能是 CDN 覆盖后的头。所以要验证最终效果,必须直接看 CDN 返回的响应头,而不是看源站:
curl -sI https://static.example.com/assets/app.3f2a1b9c.js | grep -i -E 'cache-control|expires|age'六、HTTP/2 与并发连接数的真实收益
早期推广域名分离的一个重要理由是「浏览器对同域并发连接数有限制,多开域名能多下载」。在 HTTP/1.1 时代这确实有效,六个域名就是六倍的并发。但在 HTTP/2 下,一条连接就能多路复用几十个请求,这个理由基本失效了。
现在再做域名分离,收益主要来自 Cookie 隔离和缓存策略分开管理,而不是并发数。如果站点已经全站 HTTP/2,拆到四五个域名反而可能有害:每个域名都要重新建连、重新做 TLS 握手,在首次访问时明显变慢。
所以判断标准要更新为:分一个静态域名通常有益;分三四个就属于过度优化。真的需要多域名时,优先在一个域名下用 HTTP/2 的优先级和 preload 来解决关键资源顺序问题,而不是靠域名数量。
七、上线顺序与回退方案
域名分离是一个影响全站的改动,顺序错了会立刻出事。稳妥的做法分四步。
第一步,先把静态域名配好,能正常返回资源,此时 HTML 里的引用还没改,等于一个空跑,验证 DNS、证书、CORS 三件事都正常。第二步,改一部分页面的引用,观察错误日志里有没有新增 404 和 CORS 报错。第三步,全站切换,同时保留主站原本的静态路径一个月的兼容期,保证历史外链、邮件模板、第三方页面引用的旧地址仍然能打开。第四步,一个月后再清理旧路径。
# 兼容期:主站原来的静态路径 301 到新域名
location ~* ^/(uploads|assets)/(.*)$ {
return 301 https://static.example.com/$1/$2;
}
# 注意 301 会被浏览器长期缓存,调试期先用 302回退也很简单:把 HTML 里的静态域名改回主站域名即可,因为兼容期旧路径一直可用。这就是为什么兼容期不能省:它是你的回退按钮。
八、小结
静态资源分离不是越多越好,它解决的是 Cookie 冗余、缓存策略冲突和带宽占用这三类具体问题。做的时候先统计再动手,优先用子域名而不是拆服务器,用 DNS 与 Cookie 的 Domain 属性把 Cookie 真正隔离,字体要配齐 CORS 与 preload,缓存期限按文件名是否带哈希来分,兼容期一个月保证可回退。最后记住一点:在 HTTP/2 普及之后,靠域名数量提升并发的时代已经过去了,分离的价值回到了缓存与隔离本身。