Deploying a NextJS App to Azure Web Apps

Azure sometimes feels a step behind when it comes to deploying JS apps. There are certainly many different ways to deploy apps, but I was hoping for something I wouldn’t have to manage, so no VMs or Container set ups, preferably a web app. Deploying NextJS to a Web App though, is a pain.

Here’s how I did it.

Enable Standalone mode in next.js

This outputs only the necessary files and dependencies as well as a server.js file which will run the site. These will be output into a /standalone folder. You’ll also need to copy static files into this directory if serving them as well.

Add this to your next.config.js file:

module.exports = { output: ‘standalone’ }

Azure Web App Node.js containers include pm2, a process manager for node that will start and manage running the node app. We’ll run the server.js script with pm2 on the Azure Web App, which will automatically restart the script if it stops or crashes.

For this we need an ecosystem.config.js file to identify the script and set up the options:

module.exports = {
  apps: [
    {
      name: "next-web",
      script: "server.js",
      watch: false,
      autorestart: true,
    },
  ],
};

Set up an ADO Pipeline that will run the build, copy any static files if needed and archive them for the release pipeline.

trigger:
 branches:
  include:
    - main
 paths:
   include:
     - web/*

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '18.x'
  displayName: 'Install Node.js'

- script: |
    npm ci
    npx playwright install --with-deps chromium
    npm run test:e2e
    npm run build
    ls -alh
    cp -r ./public ./.next/standalone
    cp -r ./.next/static ./.next/standalone/.next/static
    cp ./ecosystem.config.js ./.next/standalone/ecosystem.config.js
    rm ./.next/standalone/package.json
  workingDirectory: 'web'
  displayName: 'npm install, build and test'

- task: CopyFiles@2
  inputs:
    SourceFolder: './web/.next/standalone'
    Contents: '**/*'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'
    cleanTargetFolder: true

- task: PublishTestResults@2
  displayName: 'Publish test results'
  inputs:
    searchFolder: 'web/test-results'
    testResultsFormat: 'JUnit'
    testResultsFiles: 'e2e-junit-results.xml' 
    mergeTestResults: true
    failTaskOnFailedTests: true
    testRunTitle: 'Web End-To-End Tests'
  condition: succeededOrFailed()

- task: ArchiveFiles@2
  displayName: 'Archive web files'
  inputs:
    rootFolderOrFile: '$(Build.ArtifactStagingDirectory)'
    includeRootFolder: false
    archiveType: 'zip'
    archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
    replaceExistingArchive: true


- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: Tubular Web App'
  inputs:
    pathtoPublish: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'

Then in the release pipeline, we can create a web app deploy job and run:

pm2 start /home/site/wwwroot/ecosystem.config.js --no-daemon

Azure Release Pipeline

That should do it.