Your Next.js static export works when you click around, but opening /about directly or refreshing the page returns a 404. The cause is the file layout Next.js writes, and the fix is one line in next.config.js.
Quick fix
// next.config.js
module.exports = {
output: 'export',
trailingSlash: true,
}
Rebuild, deploy the new out/ folder, and /about works on any static file server.
Why the URL returns 404
By default, the export writes one HTML file per route, named after the route:
out/
├── 404.html
├── about.html
├── blog/
│ └── hello.html
└── index.html
Clicking a <Link> works because the Next.js router navigates in the browser. It fetches the route's data file (about.txt) instead of loading /about as a page. A direct visit or a refresh sends a real request for /about. A plain static server looks for a file or folder called about, finds neither, and returns 404. It doesn't know that /about should mean about.html.
Here's the default export served with python3 -m http.server:
/ 200
/about 404
/about/ 404
/about.html 200
/blog/hello 404
What trailingSlash: true changes
With trailingSlash: true, each route gets its own folder with an index.html inside:
out/
├── 404.html
├── 404/
│ └── index.html
├── about/
│ └── index.html
├── blog/
│ └── hello/
│ └── index.html
└── index.html
Every static server knows to serve index.html for a folder, so the same Python server now returns:
/ 200
/about 301 → /about/
/about/ 200
/blog/hello 301 → /blog/hello/
/blog/hello/ 200
The server redirects /about to /about/ and then serves the page. <Link> also renders href="/about/", so internal links point straight at the canonical URL without the redirect.
This blog runs as a Next.js static export with trailingSlash: true for the same reason.
Fix it on the server instead
If you want to keep URLs without a trailing slash, leave the config alone and teach the server to try .html. On nginx:
location / {
try_files $uri $uri.html $uri/ =404;
}
error_page 404 /404.html;
Against the default export:
plain nginx with try_files
/about 404 200
/blog/hello 404 200
/nope 404 404
/about/ with a slash still returns 404 here, because there's no about/ folder. If people link to your site with trailing slashes, trailingSlash: true is the simpler fix.
On S3 or another bucket with no rewrite rules, you can't do this, so use trailingSlash: true.
Why it works locally and breaks in production
npx serve out maps /about to about.html on its own. Against the default export it returns 200 for /about, /about/, and /blog/hello, so the bug never shows up when you test locally. Some hosts do the same mapping and others don't. Before you deploy somewhere new, test with a server that doesn't, like python3 -m http.server from inside out/.
next export is gone
Older answers tell you to run next build && next export. In current Next.js, that command fails:
`next export` has been removed in favor of 'output: export' in next.config.js.
Set output: 'export' in the config and run next build. The export lands in out/.
Common mistakes
Deploying the old out/ folder. trailingSlash changes the files on disk, so you have to rebuild. Check that out/about/index.html exists before you upload.
Hardcoding links without the slash. A plain <a href="/about"> still works through the redirect, but every click costs an extra round trip. Use <Link>, or write the slash yourself.
Changing the setting on a live site without redirects. Switching to trailingSlash: true changes every canonical URL from /about to /about/. Search engines treat those as different pages, so make sure the old URLs redirect (static servers usually do this for folders, as shown above) and update your sitemap.
Conclusion
A Next.js static export returns 404 on direct visits because /about is saved as about.html and static servers don't add the extension. Set trailingSlash: true so each route becomes about/index.html, or add try_files $uri $uri.html on nginx if you control the server.