Hosting Next.js apps on Azure Web Apps
Posted 24 November 2022, 02:52 | by Ben Duguid | Perma-link
We've started using Storybook for a few of the sites we're pulling together at work, while continuing to use Azure DevOps and deploying into our Azure estates. Running a simple static app produced from a Next.js site is pretty straightforward, but it turns out that running a full Next.js application on Azure App Service has a few less than obvious hoops you need to jump through to get everything lined up and working.
The key to solving this was the "With Docker" example, which provided a decent working point to compare what we had, and what we need.
Configure Next.js Output: Standalone
The main requirement is to ensure that your next.js application is configured to output as a Standalone application. In next.config.js
, ensure you've set:
module.exports = {
output: 'standalone',
}
This will create two core folders in the .next
output folder:
standalone
- This is the self contained application, including the required node_modules packages, along with a minimalserver.js
file which can be used instead ofnext start
.static
- This is the standard static site which needs to be deployed as well.
Finally, you also have your existing public
folder.
Set up your App Service
We created a Linux based App Service Plan, in our case a B1 instance.
The App Service is configured with:
- Stack:
Node
- Major version:
Node 16
- Minor version:
Node 16 LTS
- Startup Command: Provided by deployment
Everything else as you would normally configure it (e.g. FTP disabled, HTTP 2.0, HTTPS Only On, Minimum TLS Version 1.2, etc.).
Build and Deployment
Using Azure DevOps pipelines the following tasks will create a deployable package:
- stage: Build
jobs:
- job: NextBuild
pool:
vmImage: ubuntu-latest
steps:
- script: |
yarn install --frozen-lockfile
workingDirectory: $(Build.SourcesDirectory)
env:
CI: true
displayName: "Installing packages"
# Assumes output: 'standalone' configured on the next.config.
- script: |
yarn build
workingDirectory: $(Build.SourcesDirectory)
env:
CI: true
NODE_ENV: "production"
displayName: "Building Next application"
- task: CopyFiles@2
inputs:
sourceFolder: $(Build.SourcesDirectory)/.next/standalone
contents: |
**/*
targetFolder: $(Build.ArtifactStagingDirectory)/site-deploy
displayName: 'Copy standalone into the root'
- task: CopyFiles@2
inputs:
sourceFolder: $(Build.SourcesDirectory)/.next/static
contents: |
**/*
targetFolder: $(Build.ArtifactStagingDirectory)/site-deploy/.next/static
displayName: 'Copy static into the .next folder'
- task: CopyFiles@2
inputs:
sourceFolder: $(Build.SourcesDirectory)/public
contents: |
**/*
targetFolder: $(Build.ArtifactStagingDirectory)/site-deploy/public
flattenFolders: false
displayName: 'Copy Public folder'
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: $(build.artifactStagingDirectory)/site-deploy
includeRootFolder: false
archiveType: "zip"
archiveFile: $(Build.ArtifactStagingDirectory)/Nextjs-site.zip
replaceExistingArchive: true
displayName: "Package Next application"
- task: PublishPipelineArtifact@1
inputs:
artifactName: Nextjs-site
targetPath: $(Build.ArtifactStagingDirectory)/Nextjs-site.zip
displayName: "Publish Next Application artifact"
Obviously you'll need to update the workingDirectory
and sourceFolder
paths as appropriate to your repo.
Basically, the steps are:
- Restore your packages
- Build your application
- Copy the contents of the
.next/standalone
folder into the root of a staging folder - Copy the contents of the
.next/static
folder into the same folder within the staging folder - Copy the content of the
public
folder into the same folder within the staging folder - Create a zip archive of the staging folder, without including the root folder
- Publish the archive as a Pipeline Artifact
The published artifact should now contain the following structure:
+ /.next
¦ + /server
¦ ¦ + /chunks
¦ ¦ + /pages
¦ + /static
¦ + /chunks
¦ + /[hash]
+ /node_modules
+ /public
+ .env
+ package.json
+ server.js
Deployment is simply a case of deploying this package with the AzureWebApp
task:
- stage: Deploy
dependsOn: Build
condition: and(succeeded())
jobs:
- deployment: NextJs
environment: "QA"
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
appType: webAppLinux
azureSubscription: azureRMServiceConnection
appName: NextWebAppName
package: $(Pipeline.Workspace)/Nextjs-site.zip
deploymentMethod: "zipDeploy"
runtimeStack: 'Node|16-lts'
startupCommand: 'node server.js'
displayName: "Deploy site to Azure"
The important settings to call out are:
appType
- Needs to be set towebAppLinux
so that we can use thestartupCommand
.runtimeStack
- Allows the development team to adjust the major and minor versions if necessary for their specific build.startupCommand
- Needs to be set tonode server.js
. This tells the App Service container to call theserver.js
file from node, starting your application ready to receive requests.
And with that, you should now have a NextJS application running on a Linux based Azure App Service.
Filed under: Azure, DevOps, Next.js