将React、Vue或Angular项目部署到AWS上时,S3加CloudFront几乎是标准组合:S3负责存放静态文件,CloudFront负责全球CDN加速。但很多团队上线后都会遇到同一个问题——发版之后页面没变化,或者用户隔天才看到新版本;又或者一次发版后部分用户直接白屏。这些现象几乎都与CloudFront的缓存策略配置有关。单页应用有其特殊性:入口文件index.html必须保持最新,而带哈希指纹的js和css文件则希望缓存得越久越好。这两类文件的缓存要求完全相反,如果用同一套缓存规则,必然顾此失彼。

一、理解SPA的文件结构与缓存需求差异
先看一个典型的打包产物。以Vite构建的Vue项目为例,dist目录大致是这样的结构:
dist/ ├── index.html ├── assets/ │ ├── index-a1b2c3d4.js │ ├── index-e5f6a7b8.css │ └── logo-c3d4e5f6.png └── favicon.ico
这里的关键在于文件名中的哈希值。现代打包工具(Webpack、Vite、Angular CLI)默认都会为产物文件生成内容哈希,文件内容一变,文件名就变。这意味着assets目录下的文件是“内容寻址”的——只要文件名不变,内容一定不变,可以放心地永久缓存。
而index.html完全不同。它的内容引用了具体的哈希文件名,每次发版都会变化。如果CloudFront把index.html缓存了24小时,用户在发版后24小时内访问,拿到的还是旧的index.html,引用的可能是已经被删除的旧js文件,而旧文件在S3上已被新版本覆盖删除,结果就是加载失败、白屏。
所以SPA缓存策略的核心原则可以总结为一句话:index.html不缓存或短缓存,带哈希的静态资源长缓存。后面所有的配置都围绕这个原则展开。
二、CloudFront缓存策略的具体配置方法
CloudFront的缓存行为由“缓存策略”和“源响应缓存头部”共同决定。最推荐的做法是让源(S3或你的构建脚本)直接返回正确的Cache-Control头部,CloudFront遵循源站头部即可。配置路径是在CloudFront分发下的Behaviors中创建规则。
1. 为静态资源设置长缓存
第一条Behavior匹配/assets/*(根据你的实际目录调整,Angular默认是*.js、*.css按后缀匹配),缓存策略选择CachingOptimized(TTL为1天,可在自定义策略中调长到1年),源响应头部保留Cache-Control。
2. 为index.html设置禁用或短缓存
默认行为(Default *)对应index.html等入口文件。这里建议创建一个自定义缓存策略,将最小TTL设为0,最大TTL设为60秒,并在源上明确返回Cache-Control头部。
如果直接用S3作为源,可以在部署脚本中为index.html设置元数据:
# 带哈希的静态资源,缓存一年 aws s3 sync dist/assets s3://my-bucket/assets \ --cache-control "public,max-age=31536000,immutable" # index.html 每次都校验,不缓存 aws s3 cp dist/index.html s3://my-bucket/index.html \ --cache-control "no-cache"
注意no-cache和no-store的区别:no-cache的意思是可以缓存但每次使用前必须向服务器重新验证(配合ETag或Last-Modified可以避免重复传输),而no-store是完全不存。对index.html来说no-cache更合适,配合CloudFront的转发验证,既保证新鲜度又不浪费带宽。
3. 处理Behavior的匹配顺序
CloudFront按顺序匹配路径,先匹配到的规则生效。所以要把/assets/*的具体规则放在前面,Default规则兜底。一个常见的错误是把通配规则放在前面,导致所有文件都走了长缓存,index.html也被缓存住了。
三、路由刷新404与错误页重写
SPA使用前端路由(React Router、Vue Router的history模式、Angular Router),用户直接访问/user/profile这样的路径时,请求会到达CloudFront并转发给S3,但S3里根本没有这个对象,于是返回403或404。解决方法是利用CloudFront的自定义错误响应,把403/404重写到index.html并返回200状态码。
在CloudFront控制台的Error Pages中添加两条规则:
- HTTP错误码403,响应页面路径填
/index.html,响应码选200,错误缓存TTL设为0 - HTTP错误码404,同样配置,响应页面路径
/index.html,响应码200,错误缓存TTL设为0
这里有个细节必须注意:错误缓存TTL一定要设为0。如果设为默认的5分钟,发版后用户访问一个深层路由,CloudFront可能把“旧版index.html作为404响应”的结果缓存起来,导致新版本迟迟无法生效。另一个方案是使用CloudFront Functions在边缘节点做URL重写,直接把非文件请求改写为请求index.html,这样连404都不会产生,性能更好:
function handler(event) {
var request = event.request;
var uri = request.uri;
// 如果URI不含文件扩展名,视为前端路由,改写为index.html
if (!uri.includes('.')) {
request.uri = '/index.html';
}
return request;
}这个函数绑定到分发上即可。相比错误页重写,它避免了每次都先去S3请求一次失败路径,响应更快,也不会有错误缓存的问题。两种方案任选其一,CloudFront Functions方案是当前更主流的做法。
四、发版失效与自动化部署最佳实践
按照前面的配置,理论上发版后用户最多等一个TTL周期就能看到新版,但很多团队还是习惯在发版时主动调用失效。这里要理解CreateInvalidation的作用和成本:前1000条路径免费,超出后每条路径收费。一个高效的技巧是只失效index.html这一条路径,因为哈希文件都是新文件名,不存在旧缓存问题:
aws cloudfront create-invalidation \ --distribution-id XXXXXXXXXX \ --paths "/index.html"
把它集成到CI/CD流水线中,完整的部署顺序是:构建产物、同步assets到S3并设长缓存、上传index.html并设no-cache、失效CloudFront的index.html路径。顺序很重要——必须先上传新的哈希文件,再更新index.html,否则会出现index.html引用了还不存在的文件,产生短暂的404。
最后再提一个容易忽略的点:如果你的S3桶开了“静态网站托管”,源要用网站端点而不是REST API端点,因为REST端点默认不支持Content-Type为html的自动识别,且私有桶需要配合OAC(Origin Access Control)访问。目前AWS官方推荐的架构是:禁用静态网站托管,用OAC限制只有CloudFront能读桶,安全性和缓存头部支持都更好。
总结一下,SPA在CloudFront上的缓存配置就是三件事:哈希资源一年长缓存、index.html不缓存、错误页或边缘函数兜底前端路由。把这三点做对,配合CI/CD的部署顺序,就能实现秒级生效、零白屏的发版体验。
CloudFrontSPA缓存策略CDN部署修改时间:2026-09-02 08:12:33