You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js App Router部署至Azure App Service耗时42分钟优化咨询

Next.js应用部署Azure App Service CI/CD流水线耗时过长问题

我目前使用的CI/CD流水线将Next.js App Router应用部署到Azure App Service,但整个部署流程耗时长达42分钟,这个速度完全无法接受。

流水线耗时截图

配置详情

App Service配置

App Service配置

机器上的文件结构

机器文件结构

流水线配置

主流水线配置

name: Deploy Next.js to Azure Web App

on:
  workflow_dispatch:

env:
  NODE_VERSION: 20
  NEXT_APP_DIR: src/FinPhlo.Web

jobs:
  build:
    name: Build and Publish
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: ${{ env.NEXT_APP_DIR }}
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Detect package manager
        id: detect-package-manager
        run: |
          if [ -f "yarn.lock" ]; then
            echo "manager=yarn" >> $GITHUB_OUTPUT
            echo "command=install" >> $GITHUB_OUTPUT
            echo "runner=yarn" >> $GITHUB_OUTPUT
            echo "cache-dependency-path=${{ env.NEXT_APP_DIR }}/yarn.lock" >> $GITHUB_OUTPUT
            exit 0
          elif [ -f "package.json" ]; then
            echo "manager=npm" >> $GITHUB_OUTPUT
            echo "command=ci" >> $GITHUB_OUTPUT
            echo "runner=npx --no-install" >> $GITHUB_OUTPUT
            echo "cache-dependency-path=${{ env.NEXT_APP_DIR }}/package-lock.json" >> $GITHUB_OUTPUT
            exit 0
          else
            echo "Unable to determine package manager"
            exit 1
          fi

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: ${{ steps.detect-package-manager.outputs.manager }}
          cache-dependency-path: ${{ steps.detect-package-manager.outputs.cache-dependency-path }}
          
      - name: Restore cache
        uses: actions/cache@v4
        with:
          path: |
            .next/cache
          # Generate a new cache whenever packages or source files change.
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
          # If source files changed but packages didn't, rebuild from a prior cache.
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-

      - name: Install dependencies
        run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
      
      - name: Build with Next.js
        run: ${{ steps.detect-package-manager.outputs.runner }} next build --no-lint
        
      # Move the .next/static and public folders into the standalone directory.
      # The minimal server.js file does not copy these folders by default
      # because they are ideally handled by a CDN for performance optimization.
      - name: Move static and public folders
        run: |
          mv .next/static .next/standalone/.next/static
          mv public .next/standalone/public
        
      - name: Zip artifact for deployment
        run: zip ../../web.zip ./* .next -qr

      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: web-app # Name of the artifact
          path: web.zip
          retention-days: 10

  deploy-testing:
    name: Deploy
    needs: build
    uses: phlo-systems/finphlo-net/.github/workflows/reusable-workflow.yml@main
    permissions:
      id-token: write
    with:
      environment: Testing
      artifact_name: web-app
    secrets: inherit

  deploy-staging:
    name: Deploy
    needs: build
    uses: phlo-systems/finphlo-net/.github/workflows/reusable-workflow.yml@main
    permissions:
      id-token: write
    with:
      environment: Staging
      artifact_name: web-app
    secrets: inherit
    
  deploy-production:
    name: Deploy
    needs: [build, deploy-staging]
    uses: phlo-systems/finphlo-net/.github/workflows/reusable-workflow.yml@main
    permissions:
      id-token: write
    with:
      environment: Production
      artifact_name: web-app
    secrets: inherit

可复用流水线配置

name: Reusable workflow

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      artifact_name:
        required: true
        type: string
    secrets:
      AZURE_CLIENT_ID:
        required: true
      AZURE_TENANT_ID:
        required: true
      AZURE_SUBSCRIPTION_ID:
        required: true
      AZURE_AD_B2C_CLIENT_ID:
        required: true
      AZURE_AD_B2C_CLIENT_SECRET:
        required: true
      AZURE_AD_B2C_BACKEND_APP_CLIENT_ID:
        required: true
      NEXTAUTH_SECRET:
        required: true

jobs:
  deploy:
    name: ${{ inputs.environment }}
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    environment:
      name: ${{ inputs.environment }}
      url: ${{ steps.deployment.outputs.webapp-url }}
    concurrency: ${{ inputs.environment }}
    steps:
      - name: Diagnostics
        run: |
          echo "Environment: ${{ inputs.environment }}"
          echo "Artifact Name: ${{ inputs.artifact_name }}"
          echo "AZURE_APP_SERVICE_NAME: ${{ vars.AZURE_APP_SERVICE_NAME }}"
          echo "AZURE_AD_B2C_TENANT_NAME: ${{ vars.AZURE_AD_B2C_TENANT_NAME }}"
          echo "AZURE_AD_B2C_PRIMARY_USER_FLOW: ${{ vars.AZURE_AD_B2C_PRIMARY_USER_FLOW }}"
          echo "AZURE_AD_B2C_FINPHLO_RESET_PASSWORD_USER_FLOW: ${{ vars.AZURE_AD_B2C_FINPHLO_RESET_PASSWORD_USER_FLOW }}"
          echo "NEXT_SIGNALR_URL: ${{ vars.NEXT_SIGNALR_URL }}"

      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: ${{ inputs.artifact_name }}

      - name: Azure login
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Deploy to Azure Web App
        id: deployment
        uses: azure/webapps-deploy@v3
        with:
          app-name: ${{ vars.AZURE_APP_SERVICE_NAME }}
          package: web.zip

      - name: Configure Azure Web App settings
        uses: azure/appservice-settings@v1
        with:
          app-name: ${{ vars.AZURE_APP_SERVICE_NAME }}
          app-settings-json: |
            [
              {
                "name": "AZURE_AD_B2C_TENANT_NAME",
                "value": "${{ vars.AZURE_AD_B2C_TENANT_NAME }}",
                "slotSetting": false
              },
              {
                "name": "AZURE_AD_B2C_CLIENT_ID",
                "value": "${{ secrets.AZURE_AD_B2C_CLIENT_ID }}",
                "slotSetting": false
              },
              {
                "name": "AZURE_AD_B2C_CLIENT_SECRET",
                "value": "${{ secrets.AZURE_AD_B2C_CLIENT_SECRET }}",
                "slotSetting": false
              },
              {
                "name": "AZURE_AD_B2C_PRIMARY_USER_FLOW",
                "value": "${{ vars.AZURE_AD_B2C_PRIMARY_USER_FLOW }}",
                "slotSetting": false
              },
              {
                "name": "AZURE_AD_B2C_FINPHLO_RESET_PASSWORD_USER_FLOW",
                "value": "${{ vars.AZURE_AD_B2C_FINPHLO_RESET_PASSWORD_USER_FLOW }}",
                "slotSetting": false
              },
              {
                "name": "AZURE_AD_B2C_BACKEND_APP_CLIENT_ID",
                "value": "${{ secrets.AZURE_AD_B2C_BACKEND_APP_CLIENT_ID }}",
                "slotSetting": false
              },
              {
                "name": "NEXTAUTH_SECRET",
                "value": "${{ secrets.NEXTAUTH_SECRET }}",
                "slotSetting": false
              },
              {
                "name": "NEXTAUTH_URL",
                "value": "${{ steps.deployment.outputs.webapp-url }}",
                "slotSetting": false
              },
              {
                "name": "NODE_TLS_REJECT_UNAUTHORIZED",
                "value": "0",
                "slotSetting": false
              },
              {
                "name": "NEXT_PUBLIC_API_BASE_URL",
                "value": "${{ vars.NEXT_PUBLIC_API_BASE_URL }}",
                "slotSetting": false
              },
              {
                "name": "NEXT_SIGNALR_URL",
                "value": "${{ vars.NEXT_SIGNALR_URL }}",
                "slotSetting": false
              }
            ]

我的猜测

我怀疑是以下两个步骤导致构建速度过慢,但不知道正确的处理方式应该是怎样的:

- name: Move static and public folders
        run: |
          mv .next/static .next/standalone/.next/static
          mv public .next/standalone/public
        
      - name: Zip artifact for deployment
        run: zip ../../web.zip ./* .next -qr

注:该应用使用的是Next.js App Router。


内容的提问来源于stack exchange,提问作者nop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:50:05