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

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:

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 (@/*)? ... No

Configuring 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',
}

Next.js documentation

What does this configuration do?

Additional Notes

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 login

This 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-rg

This 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 P1V3

Here, 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-runtimes

This 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 --xml

Finally, 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:

  1. Copy the output from the last Azure CLI command (az webapp deployment list-publishing-profiles) to your clipboard.
  2. Navigate to Settings > Secrets and variables > Actions in your GitHub repository.
  3. Click on New repository secret, name it AZURE_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:

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/static

Adjusting 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 path

Updating 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 path

By 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/.

Screenshot of the GitHub Action with two stages; build and deploy
Screenshot of the GitHub Action successfully ran

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.