21

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;
    }

}
Industrial
  • 41,400
  • 69
  • 194
  • 289

6 Answers6

34

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.

ChrisC
  • 916
  • 1
  • 10
  • 24
  • 2
    Thanks, this worked for me. Having an actual 404 is important -- if a JS file is requested that doesn't exist and HTML is returned instead, the browser tries to parse it as JS and throws all kinds of JS errors. That could kind of screw up a SPA. – ccnokes May 11 '15 at 16:58
  • 1
    This should be higher up. Simple, elegant, and 404 for files is important indeed – Igonato Feb 25 '16 at 12:42
20

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.

jney
  • 1,862
  • 2
  • 17
  • 21
16

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;
   }
}
Adam Waite
  • 19,175
  • 22
  • 126
  • 148
7

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.

Community
  • 1
  • 1
pxwise
  • 1,350
  • 15
  • 16
0

Since there may be ajax request api, the following suits for this case,

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;
    }

    # The location block above provides the shortest prefix, of length one, 
    # and so only if all other location blocks fail to provide a match, 
    # this block will be used.

    # Ajax api starts with /v1/ will be proxied
    location /v1/ {
        proxy_pass http://proxy;
    }
}
Arthur Xu
  • 441
  • 1
  • 5
  • 12
0

I tried with try_files $uri /index.html; but nginx kept complaining with error rewrite or internal redirection cycle while internally redirecting to "/index.html".

I liked the solution Arthur Xu presented, but simplified it by rewriting all URL't that don't have a . to /index.html.

  location / {
    rewrite ^[^.]+$ /index.html last;
    proxy_pass http://web:8080;
    ...

The above rewrites all URL's without . in them to /index.htm'.

Jarppe
  • 173
  • 4
  • 7