How to Fix: Static deployment with nginx web server routes are not loaded when page is refreshed (F5)
Encountering 404 errors when refreshing a page or directly accessing a deep link in your Next.js application, even after a successful static deployment with Nginx, is a classic symptom of a misconfiguration where the web server doesn’t understand your application’s client-side routing. This isn’t a bug in Next.js itself, but rather a fundamental mismatch between how traditional web servers serve files and how modern Single Page Applications (SPAs) manage their routes.
Table of Contents
Understanding the Root Cause
When Next.js is configured for static export (using output: 'export' in next.config.js), it generates a set of static HTML, CSS, and JavaScript files into an out directory. Crucially, all internal routes are handled by client-side routing, meaning JavaScript on the browser intercepts navigation and updates the DOM without requesting a new full page from the server.
Consider a Next.js app with a route like /about. After a static build, there isn’t necessarily a physical about.html file (unless specifically configured for HTML export per route). Instead, the main entry point, index.html, along with its associated JavaScript bundles, is responsible for rendering the /about content dynamically.
The problem arises when a user directly accesses yourdomain.com/about or refreshes the page while on yourdomain.com/about. In this scenario, the browser makes a direct request to the Nginx web server for the resource at /about. By default, Nginx is a file server. It looks for a file or directory named about within its configured document root. Since no such physical file or directory exists (the content for /about is dynamically rendered by the SPA via index.html), Nginx returns a 404 Not Found error. It doesn’t know to serve index.html and let the client-side router take over.
Step-by-Step Solution
The solution involves configuring Nginx to intelligently serve the main index.html file whenever a requested URI doesn’t correspond to an existing physical file or directory, thereby handing control back to your Next.js application’s client-side router.
Pre-requisites
- A Next.js project with
output: 'export'configured innext.config.js. - Your Next.js application built using
npm run build, resulting in anoutdirectory containing your static assets. - Nginx installed and running on your server.
- The contents of your Next.js
outdirectory deployed to a specific path on your server (e.g.,/var/www/your-app).
Configure Nginx for SPA Routing
You need to modify your Nginx server block configuration. This is typically located in files like /etc/nginx/sites-available/your-site.conf or directly within /etc/nginx/nginx.conf.
Find the server block that defines your website, and specifically the location / block. You will need to add or modify the try_files directive.
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Replace with the actual path to your Next.js 'out' directory
root /var/www/your-app/out;
index index.html index.htm;
location / {
# This is the crucial line for SPA routing
try_files $uri $uri/ /index.html;
}
# Optional: Configure error pages
error_page 404 /index.html;
location = /404.html {
internal;
}
# Optional: Cache static assets for better performance
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2|woff|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, no-transform";
try_files $uri =404;
}
# ... other Nginx configurations ...
}
Explanation of the try_files directive:
$uri: Nginx first attempts to serve the file that exactly matches the requested URI. For example, if the request is for/styles.css, it tries to find/var/www/your-app/out/styles.css.$uri/: If$uriisn’t found, Nginx then checks if the URI corresponds to a directory. If it does, it tries to serve the default index file within that directory (e.g.,/var/www/your-app/out/some-directory/index.html)./index.html: If neither of the above matches (i.e., no physical file or directory exists at the requested URI), Nginx will internally redirect the request to/index.htmlrelative to therootdirective. This serves your Next.js application’s main entry point, allowing its client-side router to parse the original URI (e.g.,/about) and render the correct content.
After modifying your Nginx configuration, always test the syntax and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Verify Your Next.js Static Export
Ensure your next.config.js file has the correct configuration for static output:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
// Optional: If you're deploying to a sub-path, e.g., yourdomain.com/app
// basePath: '/app',
// images: { unoptimized: true } // If you're using Next/image with static export
};
module.exports = nextConfig;
Then, build your application:
npm run build
Confirm that an out directory is generated with your static files.
Testing the Solution
1. Deploy the contents of your Next.js out directory to the root path configured in Nginx (e.g., /var/www/your-app/out).
2. Open your application in a browser (e.g., http://yourdomain.com).
3. Navigate to an internal route using client-side links (e.g., click a link to /about).
4. While on the /about page, perform a hard refresh (F5 or Ctrl+R/Cmd+R). The page should load correctly without a 404.
5. Directly type an internal route URL (e.g., http://yourdomain.com/contact) into the browser’s address bar and press Enter. The page should load correctly.
Common Edge Cases
- Incorrect
rootdirective: Ensure therootdirective in your Nginx configuration points precisely to the directory containing yourindex.html(i.e., theoutdirectory from your Next.js build). A common mistake is pointing to the project root instead of the build output directory. - Nginx Permissions: The Nginx user (often
www-dataornginx) must have read access to your application’s deployment directory and all its contents. Usesudo chown -R www-data:www-data /var/www/your-appandsudo chmod -R 755 /var/www/your-appif permission issues arise. - Browser Caching: Sometimes, the browser might cache the old 404 response. Clear your browser cache or test in an incognito window after applying changes.
- Conflicting
locationblocks: If you have multiplelocationblocks in your Nginx configuration, ensure they don’t override the `location / { try_files … }` directive for your SPA’s routes. Specific location blocks (e.g., for APIs, images, etc.) should come before the generallocation /block. - Next.js
basePath: If your Next.js app is deployed under a sub-path (e.g.,yourdomain.com/my-app), you must configurebasePath: '/my-app'innext.config.jsand adjust your Nginxlocationblock accordingly:location /my-app/ { alias /var/www/your-app/out/; try_files $uri $uri/ /my-app/index.html; }Note the use of
aliasand the absolute path intry_filesfor theindex.html. - Missing
index.html: Ifindex.htmlis somehow missing from youroutdirectory or your Nginxindexdirective is incorrect, Nginx won’t know which file to serve as the default.
FAQ
Q1: Why does client-side navigation work perfectly, but refreshing or direct URL access fails with a 404?
A1: Client-side navigation is handled entirely by JavaScript within your browser after the initial index.html and app bundles have loaded. The browser intercepts clicks on internal links, updates the URL, and renders the new content without making a full server request. When you refresh or directly access a URL, your browser sends a fresh request to the Nginx server. Without the try_files directive, Nginx looks for a physical file matching that URL. Since your SPA’s routes don’t correspond to physical files, Nginx returns a 404. The try_files directive tells Nginx to fall back to serving index.html in such cases, allowing your SPA’s router to take over.
Q2: Is this solution specific to Next.js?
A2: No, this Nginx configuration pattern (using try_files $uri $uri/ /index.html;) is a standard solution for deploying any Single Page Application (SPA) – such as those built with React, Angular, Vue, or other frameworks like Svelte or Create React App – when they are statically exported and rely on client-side routing. The core problem and solution are identical regardless of the SPA framework, as long as it operates with client-side routing on static assets.
Q3: What if I need server-side rendering (SSR) or API routes in my Next.js application?
A3: This tutorial specifically addresses static deployment (output: 'export') where all content is pre-generated at build time. If your Next.js application uses Server-Side Rendering (SSR), API Routes, or Server Components (requiring a Node.js server to run Next.js), then a simple static Nginx configuration like this will not suffice. You would need to run the Next.js production server (next start) and use Nginx as a reverse proxy to forward requests to your running Node.js server. The Nginx configuration would look different, primarily involving a proxy_pass directive.