如何让CloudFront为SPA所有路由提供缓存的index.html
问题:CloudFront 托管SPA的路由缓存优化问题
架构与现状
- SPA通过CloudFront分发,源站为S3存储桶
- 访问根域名
https://example.com时响应正常,首字节响应时间50ms - 访问带路由的URL(如
https://example.com/route1)时,CloudFront会出现缓存未命中:先尝试在S3查找对应路由,收到404后才返回默认的index.html,首字节响应时间长达0.5-1秒
需求
实现所有非根路由均返回缓存的index.html,达到https://app.clickup.com的效果——任意路由都能以50ms左右的缓存命中响应。
已尝试方案
使用viewer-request类型的CloudFront函数重写请求URI,但响应时间仍在150-200ms,未达预期。函数代码如下:
function handler(event) { var request = event.request; var uri = request.uri; if (!uri.includes('.')) { // 对静态js、css或图片资源保留默认行为 request.uri = '/index.html'; } return request; }
测试命令:
curl -w "\nDNS Lookup: %{time_namelookup}s\nTCP Connect: %{time_connect}s\nTLS Handshake: %{time_appconnect}s\nFirst Byte: %{time_starttransfer}s\nTotal Time: %{time_total}s\n" -o /dev/null -s https://app.clickup.com/bla
解决方案
1. 调整CloudFront缓存策略,统一缓存键
当前函数已将非静态资源请求重写为/index.html,但如果缓存键仍基于原始请求URI,会导致每个路由生成独立缓存条目,无法共享根路径的缓存。需修改缓存策略:
- 进入CloudFront控制台,找到目标分发
- 切换到「缓存策略」标签,创建或修改缓存策略
- 在「缓存键和源请求」中,设置缓存键仅基于重写后的
/index.htmlURI(或直接指定固定缓存键如spa-index) - 为静态资源(带后缀的文件)配置单独的缓存行为,保留原始URI作为缓存键
2. 优化CloudFront函数逻辑
原函数的!uri.includes('.')判断不够严谨,可能误重写API类路径,建议调整为仅针对SPA前端路由:
function handler(event) { const request = event.request; const uri = request.uri; // 排除API路径、静态资源,仅重写SPA路由 if (!uri.startsWith('/api') && !uri.includes('.') && uri !== '/') { request.uri = '/index.html'; } return request; }
确保函数关联到所有需要重写的缓存行为,且在viewer-request阶段执行(此阶段修改URI后,缓存会基于修改后的URI生成)
3. 配置S3静态网站托管错误页面兜底
即使使用CloudFront函数,仍建议配置S3的错误页面作为兜底:
- 进入S3控制台,找到目标存储桶
- 开启「静态网站托管」,设置「错误文档」为
index.html - 避免CloudFront函数异常时,用户收到S3的404响应
4. 预热CloudFront缓存
完成配置后,主动预热/index.html的缓存:
- 使用CloudFront的「缓存失效」工具,提交
/index.html的失效请求(或直接用curl请求一次,触发边缘节点缓存该文件) - 确保所有重写后的请求都能直接命中边缘节点缓存,达到50ms左右的响应速度
内容的提问来源于stack exchange,提问作者Daniel Birowsky Popeski
相关产品推荐
相关产品推荐

