Rewriting nginx for pushState-URL's
I am trying to get nginx
to work with my pushState
-based URI handling that backbone.js
manages for me in an Javascript app.
Right now accessing URI's with one level, eg. example.com/users
works well, but not two-level or deeper URI's, such as example.com/users/all
, which is mentioned in the Backbone documentation:
For example, if you have a route of /documents/100, your web server must be able to serve that page, if the browser visits that URL directly
So, being far from acquainted with nginx's rewrite options, I am still sure that I can do something like rewrite ^ /index.html;
to redirect everything to my index.html
, but loosing out on any eventual static files (images, javascript & css) stored on the same server which I need to be able to access.
So what should I do instead with the below shown, current configuration, to make this work?
server {
listen 80;
server_name example.com;
location / {
root /var/www/example.com;
try_files $uri /index.html;
}
}
I ended up going with this solution:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
# Any route containing a file extension (e.g. /devicesfile.js)
location ~ ^.+\..+$ {
try_files $uri =404;
}
# Any route that doesn't have a file extension (e.g. /devices)
location / {
try_files $uri /index.html;
}
}
This way, at least I still get proper 404 errors if a file isn't found.
Here is what i did to my application. Every route ending with a '/' (except the root it self) will serve index.html
:
location ~ ^/.+/$ {
rewrite .* /index.html last;
}
You can also prefix your route :
Backbone.history.start({pushState: true, root: "/prefix/"})
and then :
location ~ ^/prefix/ {
rewrite .* /index.html last;
}
Or define a rule for each case.
I managed it like this:
#set root and index
root /var/www/conferences/video/;
index index.html;
#route all requests that don't serve a file through index.html
location / {
if (!-e $request_filename){
rewrite ^(.*)$ /index.html break;
}
}
With client side app paths:
/
/foo
/foo/bar
/foo/bar/baz
/foo/bar/baz/123
/tacos
/tacos/123
Use:
server {
listen 80;
server_name example.com;
root /var/www/example.com;
gzip_static on;
location / {
try_files $uri $uri/ /index.html;
}
# Attempt to load static files, if not found route to @rootfiles
location ~ (.+)\.(html|json|txt|js|css|jpg|jpeg|gif|png|svg|ico|eot|otf|woff|woff2|ttf)$ {
try_files $uri @rootfiles;
}
# Check for app route "directories" in the request uri and strip "directories"
# from request, loading paths relative to root.
location @rootfiles {
rewrite ^/(?:foo/bar/baz|foo/bar|foo|tacos)/(.*) /$1 redirect;
}
}
While @Adam-Waite's answer works for the root and paths at the root level, using if within the location context is considered an antipattern, often seen when converting Apache style directives. See: http://wiki.nginx.org/IfIsEvil.
The other answers do not cover routes with directory depth for my use case in a similar React app using react-router and HTML5 pushState enabled. When a route is loaded or refreshed within a "directory" such as example.com/foo/bar/baz/213123
my index.html file will reference the js file at a relative path and resolve to example.com/foo/bar/baz/js/app.js
instead of example.com/js/app.js
.
For cases with directory depth beyond the first level such as /foo/bar/baz
, note the order of the directories declared in the @rootfiles directive: the longest possible paths need to go first, followed by the next shallower path /foo/bar
and finally /foo
.