Serve a single-page application with an Nginx HTML fallback
Use try_files for frontend routes while preserving real asset errors, API responses, and predictable caching.
For a single-page application using history-based routes, let real files resolve normally and fall back to index.html for frontend paths. Keep API and asset paths separate so an error does not accidentally return HTML with a successful status.
Start with the frontend fallback
server {
listen 80;
server_name example.com;
root /var/www/myspa;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
A path such as /user/42 may exist only in the browser router. The fallback internally redirects that request to the HTML entry point, allowing JavaScript to choose the view. Existing files and directories are checked first, so this does not literally force every request to one file.
An existing directory without an index can still produce a directory-related error with the $uri/ check. If every non-file frontend path should use the SPA, omit that directory check as in the next example.
Separate assets, APIs, and the entry document
server {
listen 80;
server_name example.com;
root /var/www/myspa/dist;
index index.html;
location ^~ /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=2592000, immutable";
}
location ^~ /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location = /index.html {
try_files $uri =404;
add_header Cache-Control "no-cache";
}
location / {
try_files $uri /index.html;
}
}
This preserves /api/ in the upstream URI because proxy_pass has no URI suffix. Add separate handling for /api without a slash if your application uses that endpoint.
Only use the long immutable asset policy for filenames that change when their contents change, such as hashed build artifacts. no-cache allows the entry document to be stored but requires validation before reuse; it does not mean “never store.” Use no-store if preventing storage is the actual requirement. Neither setting alone guarantees an immediate update in the presence of a service worker or independently configured CDN.
The exact entry-document location returns 404 if index.html is absent, preventing an endless fallback cycle. Publish the entry document and its referenced assets coherently so clients do not receive HTML pointing to missing files.
Verify the route boundaries
Test a real frontend deep link, an existing asset, a missing asset, and an API route. A missing asset should stay a 404; an API error should stay an API response rather than become the SPA shell.
DataKit log collection can help inspect asset failures around a release. Access logs cannot establish whether JavaScript ran successfully or whether the browser displayed a blank page; inspect browser errors separately.
FAQ
Why do deep links still fail?
Check the selected server and location, the root path, the deployed entry document, and worker permissions. Also confirm the browser router's base path matches the URL where the application is hosted.
Does hash routing need a fallback?
The fragment in /#/user/42 is not sent to the server, so that route reaches /. The server still needs to serve the entry page and assets correctly.
Why does my API client receive HTML?
Its request likely missed the API location or was redirected into the frontend handler. Compare the actual request path, including any deployment prefix, with the configured API boundary.
Reference: Nginx try_files.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan