基于Github Actions和Docker部署SSR NextJS至Fargate与S3+CloudFront
Next.js SSR应用部署Fargate后添加CDN的实践方案
一、是否需要将/public和/.next/static同步到S3?
需要。这两个目录均为静态资源:
/public:存放手动上传的无需编译的静态文件(如图片、字体)/.next/static:Next.js构建生成的带哈希命名的静态资源(如JS/CSS bundle、优化后的图片)
将这些资源放到S3并通过CDN分发,可减轻Fargate服务的带宽压力、提升全球访问速度,同时利用CDN缓存策略降低成本。
二、是否应该仅构建一次,拆分产物部署到Fargate和S3?
是的,必须这么做:
- 保证静态资源与服务端代码版本一致,避免出现版本不兼容问题
- 减少重复构建的时间与资源消耗
三、正确操作流程(不使用IaC工具)
调整GitHub Workflow与Dockerfile,将构建过程移至Workflow中,统一生成产物后分别部署:
1. 修改Dockerfile
移除镜像内的构建步骤,直接使用Workflow构建好的产物,缩小镜像体积:
FROM node:23-alpine RUN npm i npm@latest -g WORKDIR /usr/src/app COPY package*.json ./ RUN npm ci --legacy-peer-deps --only=production # 复制Workflow构建的产物 COPY .next/standalone ./ COPY .next/static ./.next/static COPY public ./public EXPOSE 3000 CMD ["node", "server.js"]
注:Next.js 15默认支持
output: 'standalone',构建后生成的/.next/standalone包含运行所需最小依赖,可大幅压缩镜像体积。
2. 更新GitHub Workflow
添加构建步骤,同步静态资源到S3后再构建推送Docker镜像:
name: Deploy to AWS on: push: branches: - 'main' tags: - '*' jobs: deploy: runs-on: ubuntu-latest environment: name: production steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ vars.AWS_REGION }} # 配置Node环境并构建Next.js应用 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '23' cache: 'npm' - name: Install dependencies run: npm ci --legacy-peer-deps - name: Build Next.js app run: npm run build # 同步静态资源到S3并设置缓存策略 - name: Sync static assets to S3 run: | aws s3 sync ./public s3://${{ vars.S3_STATIC_BUCKET }}/public --delete aws s3 sync ./.next/static s3://${{ vars.S3_STATIC_BUCKET }}/_next/static --delete # 带哈希的静态资源设置永久缓存 aws s3 cp s3://${{ vars.S3_STATIC_BUCKET }}/_next/static s3://${{ vars.S3_STATIC_BUCKET }}/_next/static --metadata-directive REPLACE --cache-control "public, max-age=31536000, immutable" --recursive # public目录资源设置短缓存 aws s3 cp s3://${{ vars.S3_STATIC_BUCKET }}/public s3://${{ vars.S3_STATIC_BUCKET }}/public --metadata-directive REPLACE --cache-control "public, max-age=86400" --recursive # 原ECR镜像构建推送步骤 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag, and push image to Amazon ECR id: build-image env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: ${{ vars.ECR_REPOSITORY }} IMAGE_TAG: ${{ github.sha }} run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" # 原ECS部署步骤 - name: Download task definition run: | aws ecs describe-task-definition --task-definition ${{ vars.ECR_REPOSITORY }} \ --query taskDefinition > task-definition.json - name: Fill in the new image ID in the Amazon ECS task definition id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 with: task-definition: task-definition.json container-name: ${{ vars.ECR_REPOSITORY }} image: ${{ steps.build-image.outputs.image }} environment-variables: | my-env-vars - name: Deploy Amazon ECS task definition uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: task-definition: ${{ steps.task-def.outputs.task-definition }} service: ${{ vars.ECR_REPOSITORY }}-svc cluster: ${{ vars.ECS_CLUSTER }} wait-for-service-stability: true
四、最佳实践与可用GitHub Actions
最佳实践
- 缓存策略区分:
/_next/static资源带哈希后缀,设置immutable和max-age=31536000(1年),避免浏览器重复验证/public资源若无哈希,设置max-age=86400(1天),或手动给文件名添加哈希
- S3桶配置:
- 启用版本控制,防止资源误删/覆盖
- 配置桶策略允许CDN访问资源
- CDN(如CloudFront)配置:
- 设置两个源:S3(静态资源)、Fargate的ALB(SSR请求)
- 路由规则:
/_next/static/*和/public/*转发到S3,其余请求转发到ALB - 启用HTTPS,使用AWS证书管理器的证书
可用GitHub Actions
aws-actions/configure-aws-credentials:已在使用,用于配置AWS凭证actions/setup-node:配置Node环境,自带依赖缓存aws-actions/amazon-ecr-login:已在使用,登录ECRjakejarvis/s3-sync-action:替代AWS CLI的S3同步工具,配置更简洁:- name: Sync static assets to S3 uses: jakejarvis/s3-sync-action@v0.5.1 with: args: --delete --cache-control "public, max-age=31536000, immutable" env: AWS_S3_BUCKET: ${{ vars.S3_STATIC_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ vars.AWS_REGION }} SOURCE_DIR: ./.next/static
内容的提问来源于stack exchange,提问作者Royi Bernthal
相关产品推荐
相关产品推荐

