
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_modules, public, 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
| Problem | Traditional Deploy | Standalone Deploy |
|---|---|---|
| Symlinks | Relies on node_modules/.bin symlinks that break | Uses bundled server.js – no symlinks |
| Size | ~500MB+ deployment | ~50MB deployment |
| Startup | Slow Oryx tar.gz extraction (2+ min) | Instant startup |
| Build output | Must explicitly include .next folder | Everything bundled together |
| Complexity | Multiple files to coordinate | Single 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 Command:
node 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: truein 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:
- Broken symlinks – ZipDeploy doesn’t preserve
node_modules/.binsymlinks - Missing build output –
.nextfolder is gitignored and excluded from artifacts - 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:
- Enable
output: 'standalone'in Next.js config - Copy
public/and.next/static/to the standalone folder - Deploy only
.next/standalone - 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)