Fall back to default page when nginx proxy fails
I started using react router for the first time on a new web app recently. React router loads when your base page loads and then handles loading subviews as the URL changes. If using a singe JS bundle, this means that your server only serves the initial index page to the client.
This is easy to handle if the user will always start the app at the
root page (e.g. http://example.com/
), however, if they’ve navigated
and want to refresh (or if they’re navigating back to the app via a
bookmark), we need to handle that correctly on the server side.
For example, if a user navigates to http://example.com/subscribe
,
the server needs to return the index page that contains react router,
then react router will load the subscribe view.
This is straightforward with nginx1:
location / {
try_files index.html =404;
}
Things get a bit more hairy when we throw a backend API server into the mix.
In my case, I have an API backend that I want to be making calls to
from the client on the same url. E.g. POST http://example.com/user/1/subscriptions
.
This backend is a separate server listening on the machine, so we’ll
use nginx’s proxy_pass
to send requests and return them to the frontend.
The easiest way I found to do this was to make a separate named location block and refer to it from the main location block.
Failing attempts
This inital attempt fails to render anything other than API results.
location / {
try_files @api index.html =404;
}
location @api {
proxy_pass http://127.0.0.1:3000;
}
To fix this, we need to try to render static files if they exist, and only proxy to the API if there is no file with that name2.
location / {
try_files $uri $uri/ @api index.html =404;
}
location @api {
proxy_pass http://127.0.0.1:3000;
}
This works for the initial load, but now our refresh case doesn’t work
because e.g. there’s no file named subscribe
, so subscribe
gets
called on the api, but that’s not a valid API route either.
Both API calls and navigation work correctly
To get the navigation to work again, we’ll have our API return 404 for
routes it can’t handle, then we’ll have nginx render index.html
if
the API page has a 404 error.
The working block is below, note that we also set some headers as good practice (forwarded-for can be used by the API server to determine the original request).
proxy_intercept_errors_on
tells nginx that it should be responsible
for handling proxy error codes, instead of letting the API server
handle them directly.
location / {
try_files $uri $uri/ @api index.html;
}
location @api {
proxy_intercept_errors on;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:3000;
error_page 404 /index.html;
}