Automate Your Next.js Deployment to Azure: A Step-by-Step Guide

Introduction
Ever struggled with deploying a Next.js application to Azure Web Apps? You're not alone. While there are various guides available, many either lack depth or don't follow best practices. This article aims to fill that gap by providing a comprehensive, step-by-step guide to deploying your Next.js application on Azure, the right way.
We'll cover everything from setting up your development environment to the actual deployment, including essential configurations and GitHub Actions. Along the way, we'll also touch on some advanced tips for future improvements. This guide assumes you have a basic familiarity with Next.js and Azure App Services.
Ready to dive in?
Prerequisites
Before diving into the deployment process, make sure you have the following set up:
- Azure Subscription: Required to create and manage resources on Azure. Sign up here if you don't have one.
- GitHub Account: Necessary for setting up GitHub Actions for CI/CD. Create an account here.
- Node.js 18.x LTS: The runtime environment for your Next.js application. Download here.
- Yarn: A package manager that will help you manage project dependencies. Get it here.
- Azure CLI: Command-line tool to interact with Azure services. Install instructions.
- Git: Version control system to manage your codebase. Download here.
Creating the Next.js Project
Note: The current version of Next.js at the time of writing this article is 13.5.4. CLI commands and configurations may change in future versions, so be sure to check the official Next.js documentation for the most up-to-date information.
Initializing the Project
Let's start things off by creating a new Next.js project. I'll show you two ways to do this with yarn, based on how you started your project.
# If you are starting from scratch
yarn create next-app project-name
# If you started with a repository, do this inside
yarn create next-app .For this article, I've chosen specific settings while creating the application. However, these choices won't affect the overall guide.
√ Would you like to use TypeScript? ... Yes
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... Yes
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) ... Yes
√ Would you like to customize the default import alias (@/*)? ... NoConfiguring Next.js Settings
The first step in our deployment journey is to configure the Next.js application to generate a standalone folder. This folder will contain only the essential files needed for a production-ready deployment, including specific files from the node_modules directory.
To achieve this, add the following setting to your next.config.js file:
module.exports = {
  output: 'standalone',
}What does this configuration do?
- Standalone Folder: The setting will generate a .next/standalonefolder that can be deployed independently, eliminating the need to installnode_modules.
- Minimal Server.js: Alongside the standalone folder, a minimal server.jsfile is also generated. This can be used as an alternative to thenext startcommand.
Additional Notes
- The minimal server.jsdoes not include thepublicand.next/staticfolders by default. These are typically managed by a Content Delivery Network (CDN).
- If you still wish to include these folders, you can manually copy them to standalone/publicandstandalone/.next/static. Once copied, theserver.jsfile will serve these automatically.
Configuring Azure Resources
Using Azure CLI
To set up the necessary Azure resources, we'll be using the Azure Command Line Interface (CLI). Below are the commands you'll need to run, along with explanations for each:
# Log in to your Azure account
az loginThis command ensures you are logged into your Azure account. If you're not logged in, it will prompt you to do so.
# Create a new resource group in a specific location (West Europe in this case)
az group create --location westeurope --resource-group next-azure-deployment-rgThis command creates a new resource group named next-azure-deployment-rg in the westeurope location.
# Create an App Service plan
az appservice plan create --resource-group next-azure-deployment-rg --name nextazuredeployplan --is-linux --sku P1V3Here, we create an App Service plan named nextazuredeployplan using a Linux host and a specific SKU (P1V3).
# List available runtimes for the web app
az webapp list-runtimesThis command lists all the available runtimes that you can use for your web app.
# Create a web app with a Node.js runtime
az webapp create --resource-group next-azure-deployment-rg --name nextazuredeploywebapp --plan nextazuredeployplan --runtime "NODE:18-lts"This creates a new web app named nextazuredeploywebapp, using the previously created App Service plan and a Node.js runtime.
# Retrieve the publishing profile for the web app
az webapp deployment list-publishing-profiles --name nextazuredeploywebapp --resource-group next-azure-deployment-rg --xmlFinally, this command retrieves the publishing profile for your web app in XML format, which you'll need for deploying your app.
Using Azure Portal
While this guide won't cover using the Azure Portal in detail, if you're not familiar with it, I recommend sticking with the Azure CLI steps above. Alternatively, you can seek help from a colleague or refer to Microsoft Learn for more in-depth tutorials.
Setting Up GitHub Actions
In this section, we'll walk you through setting up GitHub Actions to automate the deployment of your Next.js application to Azure. We'll cover adding secrets to GitHub, configuring the workflow file, and making adjustments for environment variables and package management.
Adding Secrets to GitHub
First, let's add the necessary secrets to your GitHub repository:
- Copy the output from the last Azure CLI command (az webapp deployment list-publishing-profiles) to your clipboard.
- Navigate to Settings > Secrets and variables > Actionsin your GitHub repository.
- Click on New repository secret, name itAZURE_WEBAPP_PUBLISH_PROFILE, and paste the copied publish profile into the value field.
Configuring the Workflow File
Initial Setup
You can either navigate to the Actions tab in your GitHub repository and choose the "Deploy Node.js to Azure Web App" workflow, or you can manually create a workflow file named azure-webapps-node.yml inside a folder called .github/workflows in your repository.
File Structure
Here's a preview of what your workflow file will look like:
on:
  push:
    branches: [ "main" ]
  workflow_dispatch:
env:
  AZURE_WEBAPP_NAME: your-app-name
  AZURE_WEBAPP_PACKAGE_PATH: '.'
  NODE_VERSION: '14.x'
permissions:
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    - name: npm install, build, and test
      run: |
        npm install
        npm run build --if-present
        npm run test --if-present
    - name: Upload artifact for deployment job
      uses: actions/upload-artifact@v3
      with:
        name: node-app
        path: .
  deploy:
    permissions:
      contents: none
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: 'Development'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    steps:
    - name: Download artifact from build job
      uses: actions/download-artifact@v3
      with:
        name: node-app
    - name: 'Deploy to Azure WebApp'
      id: deploy-to-webapp
      uses: azure/webapps-deploy@v2
      with:
        app-name: ${{ env.AZURE_WEBAPP_NAME }}
        publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
        package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}Environment Variables
Update the following environment variables in your workflow file:
- AZURE_WEBAPP_NAME: Should match the name of the web app you created using Azure CLI.
- AZURE_WEBAPP_PACKAGE_PATH: Change from- '.'to- './.next/standalone'.
- NODE_VERSION: Change from- '14.x'to- '18.x'.
Switching from NPM to Yarn
Replace the NPM steps in your build job with Yarn. Here's how:
- name: Set up Node.js
  uses: actions/setup-node@v3
  with:
    node-version: ${{ env.NODE_VERSION }}
    cache: 'yarn'
- name: yarn install, build, and copy files
  run: |
    yarn --frozen-lockfile
    yarn build
    cp -R ./public ./.next/standalone/public
    cp -R ./.next/static ./.next/standalone/.next/staticAdjusting Artifact Paths
After switching from NPM to Yarn, you'll also need to adjust the paths for both uploading and downloading artifacts in your GitHub Actions workflow. This ensures that the correct build artifacts are used during the deployment process.
Updating Upload-Artifact Step
Locate the Upload artifact for deployment job step in your workflow file and update the path parameter. Here's how you can change it:
- name: Upload artifact for deployment job
  uses: actions/upload-artifact@v3
  with:
    name: node-app
    path: ./.next/standalone  # Updated pathUpdating Download-Artifact Step
Similarly, find the Download artifact from build job step and update it to align with the new upload path. Here's the updated code:
- name: Download artifact from build job
  uses: actions/download-artifact@v3
  with:
    name: node-app  # Make sure this name matches the upload step
    path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}  # Updated pathBy making these adjustments, you ensure that the correct build artifacts are uploaded and downloaded, facilitating a smooth deployment process. The path parameter in the download step is particularly important because the next step, azure/webapps-deploy@v2, uses the env.AZURE_WEBAPP_PACKAGE_PATH to upload the files to the web app.
Final Workflow File
After making all the changes, your final workflow file should look like this:
on:
  push:
    branches: ["main"]
  workflow_dispatch:
env:
  AZURE_WEBAPP_NAME: nextazuredeploywebapp
  AZURE_WEBAPP_PACKAGE_PATH: "./.next/standalone"
  NODE_VERSION: "18.x"
permissions:
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "yarn"
      - name: yarn install, build, and copy files
        run: |
          yarn --frozen-lockfile
          yarn build
          cp -R ./public ./.next/standalone/public
          cp -R ./.next/static ./.next/standalone/.next/static
      - name: Upload artifact for deployment job
        uses: actions/upload-artifact@v3
        with:
          name: node-app
          path: ./.next/standalone
  deploy:
    permissions:
      contents: none
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: "Development"
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v3
        with:
          name: node-app
          path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
      - name: "Deploy to Azure WebApp"
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v2
        with:
          app-name: ${{ env.AZURE_WEBAPP_NAME }}
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
          package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
Deploying to Azure
Changing the Startup Command
Alright, let's dive in. You might recall that our application no longer boots up with the usual npm run start or next start. Instead, we're using a server.js file. By default, Azure Web App is set to kick things off with npm run start. We need to switch that up to node server.js.
To make this change, fire up your Azure CLI and run this single command:
az webapp config set --resource-group next-azure-deployment-rg --name nextazuredeploywebapp --startup-file "node server.js"Once you hit enter, the Web App will restart and come to life with our new startup command. Neat, right?
Verifying Deployment
Time to see our hard work in action. Head over to your Azure Web App. You can find it at a URL that looks something like this: http://nextazuredeploywebapp.azurewebsites.net/.

And there you have it! Your Next.js app is live and kicking. Plus, every time you commit changes to your main branch, GitHub Actions will take the stage, triggering a new workflow and deploying an updated version of your app.
Further Reading and Next Steps
Optimize Your Workflow
Next.js Caching
After running next build in a workflow like this, Next.js will hint that CI-caching could speed up the build process. Check out the Next.js documentation to see how you can easily integrate this into your GitHub Action.
Zip Your Artifacts
If you've noticed, uploading artifacts can be time-consuming. A quick fix? Zip all the files and upload a single zip-file as an artifact. In my casual tests, this slashed the upload and download times from minutes to mere seconds.
CDN for Static Files
If your app is heavy on static files, consider using a Content Delivery Network (CDN). You can pack your public and static files into a separate artifact and upload them to a CDN in a different stage. Where to host your CDN? That's your call, Azure or elsewhere. I'll dive into this in a future article.
What's Next?
Explore Azure Features
Azure has a plethora of services that can further enhance your app. From databases to AI services, the sky's the limit.
Dive Deeper into Next.js
Next.js is a powerful framework with features like server-side rendering and static site generation. The more you know, the better your app will be.
Automate Testing
If you're serious about your project, automated testing is a must. Look into setting up a CI/CD pipeline that includes a testing stage.
Wrapping It Up
And there you have it! We've journeyed through the intricacies of Next.js and Azure, and now you should have a fully deployed Next.js application running smoothly on Azure Web Apps. Not only that, but you've also set up an automated deployment pipeline using GitHub Actions.
By diving deep into the Next.js documentation, we've uncovered a straightforward yet powerful way to get your Next.js app up and running on Azure. Along the way, we've also touched on workflow optimizations and future steps you can take to further enhance your project.
So, what's next? The ball's in your court. Whether it's optimizing your workflow, exploring Azure's vast array of services, or diving deeper into Next.js, the road ahead is full of possibilities.