How to Fix Nginx Proxying to Next.js App Router Returning Static Assets 404
Quick answer
Your Next.js app works fine in local development, but once deployed behind Nginx, CSS, JS chunks, and other static assets return 404 while the actual pages...
Your Next.js app works fine in local development, but once deployed behind Nginx, CSS, JS chunks, and other static assets return 404 while the actual pages load fine. This is almost always Nginx's location block matching logic interfering with Next.js's specific internal asset paths, particularly the _next/static directory the App Router relies on.
The Problem
Pages render, but the browser console is full of failed asset loads:
GET https://yourapp.com/_next/static/chunks/main-app-a1b2c3d4.js net::ERR_ABORTED 404 (Not Found)
Checking directly confirms Nginx is returning 404 for these specific paths rather than proxying them through to Next.js correctly:
$ curl -I https://yourapp.com/_next/static/chunks/main-app-a1b2c3d4.js
HTTP/1.1 404 Not Found
Meanwhile, requesting the same path directly against the Next.js server (bypassing Nginx) works perfectly fine.
Why It Happens
Next.js serves its built static assets β JS bundles, CSS, images processed through its image optimizer β under a specific internal path prefix, _next/static, and expects every request under that path to be routed straight through to the Next.js server process itself. When Nginx sits in front of Next.js, this fails if the Nginx configuration doesn't correctly proxy that specific path pattern. Common causes:
- A
locationblock explicitly configured to serve static files directly from disk (a common Nginx pattern for other frameworks) that doesn't actually match where Next.js's build output lives, or wasn't updated when migrating from an older Next.js setup with a different static file structure. - An overly specific
locationblock matching a subset of the_nextpath (like/_next/imagefor Next.js's image optimization endpoint) that inadvertently shadows or conflicts with the broader_next/staticpattern needed for regular assets. - Missing or incorrect
proxy_passconfiguration for the root location, where only specific application routes were proxied and the general static asset path was never explicitly accounted for. - A caching layer or CDN in front of Nginx caching a 404 response from an earlier misconfiguration, continuing to serve that stale cached error even after the underlying Nginx config was fixed.
The Fix
The most reliable configuration proxies everything β application routes and static assets alike β through to the Next.js server process, letting Next.js itself handle serving its own static files correctly rather than trying to have Nginx serve them directly from disk:
server {
listen 80;
server_name yourapp.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
This single catch-all location / block correctly routes _next/static, _next/image, application pages, and API routes all through to Next.js, avoiding the need to carefully replicate Next.js's internal routing logic in Nginx.
If you specifically want Nginx to serve static assets directly from disk for performance reasons (bypassing the Node.js process for pure static files), make sure the path and location block precisely match Next.js's actual build output structure:
location /_next/static/ {
alias /var/www/yourapp/.next/static/;
expires 365d;
access_log off;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}
Double-check the alias path matches your actual deployed build output directory exactly β a common subtle mistake is using root instead of alias (which handles the path differently) or getting the trailing slash inconsistent between the location pattern and the target path, both of which cause silent path mismatches:
ls -la /var/www/yourapp/.next/static/chunks/
Verify the files genuinely exist at the path Nginx is configured to serve from before assuming the Nginx config itself is wrong.
Still Not Working?
If the Nginx configuration looks correct and direct testing against Next.js works, but the 404 persists specifically when going through the public domain, check for a CDN or caching layer (CloudFront, Cloudflare) in front of Nginx that might be caching a stale 404 response from before your configuration fix:
curl -I https://yourapp.com/_next/static/chunks/main-app-a1b2c3d4.js -H "Cache-Control: no-cache"
If a CDN is involved, explicitly invalidate its cache for the affected paths after any Nginx configuration change, since the CDN has no way to know your origin's behavior has changed until either its cache naturally expires or you force an invalidation:
aws cloudfront create-invalidation --distribution-id YOUR_DIST_ID --paths "/_next/static/*"