Nginx SPA Refresh 404 on Static Routes and How to Fix It

1 min read

When deploying single-page applications (SPA) built with Vue, React, or Angular, you usually run into this problem: the home page loads fine and in-app navigation works too. But if you refresh the browser on a sub-route, or paste that link straight into the address bar, Nginx returns 404 Not Found.

Nginx SPA Refresh 404 on Static Routes and How to Fix It

1. The Problem and Its Background

When deploying single-page applications (SPA) built with Vue, React, or Angular, you usually hit this:

  • The home page https://example.com/ loads fine.
  • In-app navigation (for example https://example.com/about) works too, because the front-end router intercepts it.
  • But if you refresh the browser on https://example.com/about, or type that link directly into the address bar, Nginx returns a 404 Not Found error.

1.1 The Original Configuration (the broken one)

Nginx

server {
    listen       443 ssl;
    server_name  example.com;

    # SSL configuration omitted...

    location / {
        root   /usr/share/nginx/html; # assumed static file path
        index  index.html index.htm;
    }
}

1.2 Why It Happens

By default, Nginx handles requests based on the file system. When /about is requested, Nginx looks in the root directory for a file named about. If it cannot find one, it tries about/index.html. For an SPA, /about is only a front-end virtual route — there is no such physical file or directory on disk — so Nginx ends up throwing a 404.


2. The Fix: Use try_files

To solve this, we need to tell Nginx: “If the matching file or directory does not exist, don’t return 404. Instead, internally redirect every request to index.html and let the front-end code handle routing.”

2.1 The Optimized Configuration

Nginx

server {
    listen       443 ssl;
    server_name  example.com;
  
    # It is a good idea to declare the document root explicitly
    root   /usr/share/nginx/html; 

    # The SSL section stays unchanged...

    location / {
        # The key change: add the try_files directive
        # Try the file ($uri), then the directory ($uri/); if both fail, serve index.html
        try_files $uri $uri/ /index.html;
    }
}

3. How the try_files Directive Works

try_files is one of the most powerful directives in Nginx. Its syntax looks like this:

try_files $uri $uri/ /index.html;

The directive checks its arguments from left to right:

  1. $uri:
    • Check whether the requested path maps to a real file on disk (an image, JS, CSS, and so on).
    • If it does, return that file directly.
  2. $uri/:
    • If no file matches, check whether a directory with the same name exists.
    • If it does, Nginx automatically looks for the index file inside it (for example /about/index.html).
  3. /index.html:
    • If neither of the first two exists, Nginx performs an internal redirect and forwards the request to /index.html.
    • At that point the front-end index.html loads, the front-end router (Vue Router / React Router) takes over the URL, and renders the correct page from the path.

4. A Note (Optional)

If your configuration needs strict error handling, you can append =404 as a fallback. In SPA setups this is usually unnecessary unless you are sure index.html might go missing.

Nginx

# Variant: only return 404 if index.html is missing too
try_files $uri $uri/ /index.html =404;

Comments