1.
概览与目标
- 目标:构建后自动把静态资源(JS/CSS/图片/字体)上传到 CDN,并让 HTML 引用正确的 CDN 地址;实现缓存控制与高效失效。
- 前提:已有静态构建产物(例如 dist/ 或 build/ 目录)、选定 CDN(CloudFront、Cloudflare、Fastly 或对象存储如 S3 + CDN)。
- 输出:自动化脚本(本地/CI) + 构建器插件 + 上传/失效流程。
2.
选择合适的 CDN 与托管方式
- 选项比较:对象存储(S3/GCS/Azure Blob)+ CDN(CloudFront/Cloudflare CDN)适合静态站点;Cloudflare Workers 或直接 CDN 适合边缘计算需求。
- 考虑因素:地域覆盖、缓存失效成本、HTTPS 支持、自定义域与证书、价格与上传 API 支持。
- 建议:开始可用 S3 + CloudFront(通用且文档多),生产可评估 Cloudflare 以简化缓存失效与自带 CDN。
3.
资源指纹化(文件名哈希)实践
- 为什么:避免旧缓存被持续使用,同时便于长期缓存静态资源(Cache-Control: max-age=31536000)。
- 如何做:Webpack/Vite/Rollup 输出使用 contenthash 或 [hash],示例 Webpack output: filename: '[name].[contenthash:8].js'。
- 验证:构建后检查 dist/ 下文件名包含 hash,HTML 引用由构建插件自动注入正确文件名。
4.
在 Webpack 中集成 CDN 上传(具体步骤)
- 安装依赖:npm install --save-dev webpack-asset-relocator-loader aws-sdk webpack-s3-plugin 或使用 s3-cli。
- 配置 Webpack:设置 output.publicPath 为 CDN 域名(临时或生产),并启用 contenthash;使用 HtmlWebpackPlugin 生成引用。
- 上传脚本:在打包后执行脚本(npm script):先 build,再用 aws s3 sync ./dist s3://your-bucket --delete,然后 cloudfront create-invalidation --distribution-id ID --paths '/*'。
5.
Vite / Rollup 场景(示例步骤)
- Vite:vite.config.js 中设置 build.rollupOptions.output.assetFileNames 与 entryFileNames 使用 hash,并可设置 base 为 CDN 域名或 CI 时替换。
- 插件:使用 vite-plugin-s3 或自写脚本通过 aws-sdk 上传。
- 实操:在 CI 中先运行 npm run build,然后运行 node scripts/upload-to-s3.js(脚本读取 dist/ 遍历上传并设置合适 Content-Type 与 Cache-Control)。
6.
上传到 S3 并配合 CloudFront(命令与示例)
- 前提:已配置好 S3 bucket 与 CloudFront 分配(distribution)。
- 命令示例:aws s3 sync ./dist s3://my-bucket --delete --cache-control "public, max-age=31536000, immutable" --acl public-read
- CloudFront 失效:aws cloudfront create-invalidation --distribution-id E123ABC --paths "/*"(注意成本和频率,推荐只失效需要的路径)。
7.
CI/CD 中的自动化与凭据管理(Github Actions 示例)
- 步骤:把 AWS 凭据设置到 GitHub Secrets(AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY);在 workflow 中先 checkout、安装依赖、build、上传并触发失效。
- 简要 YAML 示例(核心命令行):
- - uses: actions/checkout@v2
- - run: npm ci && npm run build
- - run: aws s3 sync ./dist s3://bucket --cache-control "public, max-age=31536000, immutable" --delete
- - run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_ID }} --paths "/index.html"
8.
如何处理 HTML 引用和 publicPath 动态替换
- 方法A:构建时直接把 publicPath 设置为 https://cdn.example.com/(适用于生产构建)。
- 方法B:构建输出相对路径,在发布阶段用脚本替换 HTML 中的前缀为 CDN 域名,或者在部署后通过反向代理添加前缀。
- 注意:index.html 本身通常需要较短缓存策略(例如 no-cache 或 max-age=0),以便引用最新的哈希文件。
9.
缓存策略与失效最佳实践
- 长缓存:对带 hash 的静态资产设置 long max-age(例如 1 年),并且标注 immutable。
- 短缓存:对 index.html、app shell 设置短缓存并启用 ETag 或 Last-Modified,或使用 CloudFront 的 Origin Cache Control。
- 失效策略:尽量通过文件指纹避免频繁全站 invalidation,仅在必要时对单个文件或路径发起 invalidation。
10.
上传脚本中的细节(示例 Node.js 上传要点)
- 遍历目录:使用 fs.readdirSync 递归读取 dist/ 下文件并保留目录结构上传到目标 bucket。
- Headers:根据后缀设置 Content-Type,设置 Cache-Control(哈希文件长缓存,index.html 短缓存)。
- 并发上传:使用 p-limit 或 Promise.all 控制并发数,避免 API 限制或过多并发失败。
11.
测试、回滚与监控
- 测试:本地或 staging 环境先把构建产物上传到 staging bucket,检查 CDN 下静态文件可访问与缓存头是否正确。
- 回滚:保留历史版本或在服务器端保留旧文件,必要时修改 index.html 指向旧哈希文件或回滚到上一个 CloudFront 配置。
- 监控:开启 CDN 日志(S3 日志或 CloudFront Logging),监测 4xx/5xx、缓存命中率与带宽费用。
12.
常见问题与规避策略
- 文件名冲突:确保每次构建哈希稳定,避免手动覆盖同名文件。
- 大文件/字体:对大资源考虑分流到不同子域或使用 Range 请求;对字体注意 Access-Control-Allow-Origin。
- 自动化失败:CI 中捕获错误并回滚状态,上传脚本应返回 non-zero 以中断发布过程。
13.
问:为什么构建产物要使用内容哈希而不是 CDN 失效?
- 回答:使用内容哈希能让静态资源具备长期缓存而无需频繁触发 CDN invalidation,节省成本并提升缓存命中率。哈希改变表示资源变化,客户端与 CDN 可安全缓存旧资源直到不再被引用。
14.
问:在 CI 中如何安全地管理 CDN 凭据与避免泄露?
- 回答:将密钥存放在 CI 平台的 Secret 管理(如 GitHub Secrets、GitLab CI Variables),限制凭据权限为只读或仅 S3 上传与 CloudFront 创建失效权限,避免在日志中打印密钥。
15.
问:如何在多环境(staging/production)下切换 CDN 域名与缓存策略?
- 回答:通过环境变量或 CI 参数控制构建时的 publicPath 与上传目标(不同的 bucket/分配 ID),并在上传脚本中根据环境设置不同的 Cache-Control 与 invalidation 策略,部署流程中明确分支与权限分离。
来源:面向开发者 网站如何使用cdn 与构建流程无缝集成实践