How to Deploy Next.js app to Azure App Service using GitHub Actions: A Complete Guide

How to Deploy Next.js app to Azure App Service using GitHub Workflow- A Complete Guide - TutLinks
How to Deploy Next.js app to Azure App Service using GitHub Actions- A Complete Guide – TutLinks

Deploying a Next.js app to Azure App Service can be frustrating. You’ll likely encounter cryptic errors that don’t happen on Vercel or Netlify. After multiple failed deployments and hours of debugging, I finally cracked the code. This guide walks through the common pitfalls when you deploy Next.js to Azure App Service and provides a battle-tested solution using Next.js standalone output.

What Happens When You Deploy Next.js to Azure App Service

Let’s say you have a standard Next.js app with the default package.json:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

You set up GitHub Actions, deploy your Next.js app to Azure App Service, and… it fails.

Common Errors When Deploying Next.js to Azure

Error 1: “next: not found”

> next start
sh: 1: next: not found

What’s happening: Azure App Service uses Oryx to run Node.js applications. When GitHub Actions builds your app and deploys via ZipDeploy, the symlinks in node_modules/.bin/ are not preserved during extraction. The next command is actually a symlink to node_modules/next/dist/bin/next, and when that symlink breaks, the command isn’t found.

This is a documented issue by the Azure App Service team – symlinks are not retained when using ZipDeploy.

Common workaround (partial fix): Change the start script to point directly to the entrypoint:

{
  "scripts": {
    "start": "node node_modules/next/dist/bin/next start"
  }
}

This bypasses the symlink issue when you deploy Next.js to Azure App Service. But then you hit the next error…

Error 2: “Could not find a production build”

2026-01-05T10:33:41.114033Z echo "Done."
2026-01-05T10:33:41.1140351Z npm start
2026-01-05T10:33:41.1410803Z Found tar.gz based node_modules.
2026-01-05T10:33:41.1510535Z Removing existing modules directory from root...
2026-01-05T10:33:41.2395916Z Extracting modules...
2026-01-05T10:35:05.502Z No new trace in the past 1 min(s).
2026-01-05T10:35:22.9691488Z Done.
2026-01-05T10:35:24.4771969Z npm info using [email protected]
2026-01-05T10:35:24.4865662Z npm info using [email protected]
2026-01-05T10:35:26.4490493Z
2026-01-05T10:35:26.4491007Z > [email protected] start
2026-01-05T10:35:26.4491041Z > node node_modules/next/dist/bin/next start
2026-01-05T10:35:26.4491061Z
2026-01-05T10:35:28.4905329Z ▲ Next.js 16.1.1
2026-01-05T10:35:28.492641Z - Local:         http://localhost:8080
2026-01-05T10:35:28.493926Z - Network:       http://169.254.129.3:8080
2026-01-05T10:35:28.5004227Z
2026-01-05T10:35:28.5016097Z ✓ Starting...
2026-01-05T10:35:36.0094543Z Error: Could not find a production build in the '.next' directory. Try building your app with 'next build' before starting the production server.

What’s happening: The .next folder (your build output) is in .gitignore. When GitHub Actions uploads artifacts, it respects .gitignore by default, so your build output never makes it to Azure.

Why this is painful: You need to explicitly include .next in your deployment artifact, along with node_modulespublic, and package.json. This results in a 500MB+ deployment that takes forever to upload and extract.

The Real Problem: Oryx’s tar.gz Extraction

Notice this in the logs:

Found tar.gz based node_modules.
Removing existing modules directory from root...
Extracting modules...

Azure’s Oryx build system compresses node_modules into a tar.gz file and extracts it at runtime. This process:

  • Takes 2+ minutes on every cold start
  • Breaks symlinks in node_modules/.bin/
  • Creates a poor developer experience

The Solution: Deploy Next.js to Azure Using Standalone Output

Next.js provides a standalone output mode that solves all these problems when you deploy Next.js to Azure App Service. It creates a self-contained deployment package with only the necessary dependencies – no symlinks, no massive node_modules, no Oryx extraction delays.

What Standalone Output Contains

.next/standalone/
├── server.js          # Self-contained Node.js server
├── .next/             # Build output
├── node_modules/      # Minimal traced dependencies (~50MB vs 500MB+)
├── public/            # Static assets (copied manually)
└── package.json       # Metadata

Why Standalone Works for Next.js Azure Deployment

ProblemTraditional DeployStandalone Deploy
SymlinksRelies on node_modules/.bin symlinks that breakUses bundled server.js – no symlinks
Size~500MB+ deployment~50MB deployment
StartupSlow Oryx tar.gz extraction (2+ min)Instant startup
Build outputMust explicitly include .next folderEverything bundled together
ComplexityMultiple files to coordinateSingle server.js entry point

Step-by-Step: Deploy Next.js to Azure App Service

1. Configure Next.js for Standalone Output

Update your next.config.ts:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  // Standalone output for Azure/Docker deployments
  output: 'standalone',

  // Your other config options...
  compress: true,
  poweredByHeader: false,
};

export default nextConfig;

2. Update package.json Scripts

Add a standalone start script (optional but useful for local testing):

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "start:standalone": "node .next/standalone/server.js",
    "lint": "eslint ."
  }
}

3. Create GitHub Actions Workflow

Create .github/workflows/azure-deploy.yml:

name: Build and deploy Next.js to Azure Web App

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20.x'

      - name: Install and build
        run: |
          npm ci
          npm run build

      # Next.js standalone requires copying static assets
      - name: Prepare standalone deployment
        run: |
          cp -r public .next/standalone/
          cp -r .next/static .next/standalone/.next/

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: node-app
          path: .next/standalone
          include-hidden-files: true

  deploy:
    runs-on: ubuntu-latest
    needs: build
    permissions:
      id-token: write
      contents: read

    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: node-app
      
      - name: Login to Azure
        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
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'your-app-name'
          slot-name: 'Production'
          package: .
          startup-command: 'node server.js'

4. Configure Azure App Service

In Azure Portal, ensure your App Service has:

  • Runtime: Node.js 22 LTS (or your preferred latest version)
  • Startup Commandnode server.js

You can set the startup command via:

  • Azure Portal → App Service → Configuration → General settings → Startup Command
  • Or in the workflow with startup-command: 'node server.js'

Important: Copy Static Assets

The standalone build doesn’t include public/ or .next/static/ by default. You must copy them:

cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

This is handled in the workflow’s “Prepare standalone deployment” step.

Environment Variables

Set environment variables in Azure Portal → App Service → Configuration → Application settings.

For build-time variables (like NEXT_PUBLIC_*), add them to your GitHub Actions workflow:

- name: Install and build
  run: |
    npm ci
    npm run build
  env:
    NEXT_PUBLIC_API_URL: ${{ vars.API_URL }}

Testing Locally Before You Deploy Next.js to Azure

Before deploying, test the standalone build locally:

# Build
npm run build

# Copy static assets
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

# Run standalone server
node .next/standalone/server.js

Visit http://localhost:3000 to verify.

Troubleshooting Next.js Azure Deployment

“next: not found”

sh: 1: next: not found

Cause: Symlinks in node_modules/.bin/ are broken after ZipDeploy extraction.

Fix: Use standalone output with node server.js startup command. If you can’t use standalone, change your start script to:

"start": "node node_modules/next/dist/bin/next start"

“Could not find a production build”

Error: Could not find a production build in the '.next' directory.

Cause: The .next folder wasn’t included in the deployment artifact (it’s in .gitignore).

Fix: With standalone output, everything is bundled in .next/standalone. Ensure:

  • include-hidden-files: true in upload-artifact action
  • The artifact path is .next/standalone

“Found tar.gz based node_modules” + Slow Startup

Found tar.gz based node_modules.
Extracting modules...
(takes 2+ minutes)

Cause: Oryx compresses and extracts node_modules on every cold start.

Fix: Standalone output includes only traced dependencies (~50MB vs 500MB+), eliminating this extraction step entirely.

Static assets not loading (404)

Cause: You forgot to copy static assets to the standalone folder.

Fix: Ensure the workflow includes:

cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

Port issues

Cause: Hardcoded port in your application.

Fix: Azure App Service sets the PORT environment variable (usually 8080). The standalone server respects this automatically. Don’t hardcode ports.

Conclusion

Deploying a Next.js app to Azure App Service has historically been painful due to:

  1. Broken symlinks – ZipDeploy doesn’t preserve node_modules/.bin symlinks
  2. Missing build output – .next folder is gitignored and excluded from artifacts
  3. Slow cold starts – Oryx extracts 500MB+ of node_modules on every startup

The standalone output mode solves all of these when you deploy Next.js to Azure App Service:

  • ✅ No symlinks – just node server.js
  • ✅ Everything bundled – no missing files
  • ✅ ~50MB deployment – fast uploads and instant startup

The key steps to deploy Next.js to Azure are:

  1. Enable output: 'standalone' in Next.js config
  2. Copy public/ and .next/static/ to the standalone folder
  3. Deploy only .next/standalone
  4. Set startup command to node server.js

This approach works for Next.js 13, 14, 15, and 16 – any version that supports standalone output.

Congratulations 🎉, you have successfully learnt how to deploy NextJS app on Azure App Service using GitHub Actions workflow. Bookmark Bookmark 🔖 (Ctrl + D) this page for a quick reference.

Check these other resources to deploy on Azure App Service

Deploy FastAPI on Azure App Service

Easily Deploy WordPress on Azure App Service (A Concise Guide for Azure Architects)

Navule Pavan Kumar Rao

I am a Full Stack Software Engineer with the Product Development experience in Banking, Finance, Corporate Tax and Automobile domains. I use SOLID Programming Principles and Design Patterns and Architect Software Solutions that scale using C#, .NET, Python, PHP and TDD. I am an expert in deployment of the Software Applications to Cloud Platforms such as Azure, GCP and non cloud On-Premise Infrastructures using shell scripts that become a part of CI/CD. I pursued Executive M.Tech in Data Science from IIT, Hyderabad (Indian Institute of Technology, Hyderabad) and hold B.Tech in Electonics and Communications Engineering from Vaagdevi Institute of Technology & Science.

Leave a Reply