Static files & SPA

Single-page-app fallback to index.html

3 min · updated June 16, 2026

A single-page app routes in the browser, so a refresh on /dashboard makes Nginx look for a file that doesn’t exist. The fix is one try_files line that falls back to index.html, letting the app’s router take over.

server {
    listen 80;
    server_name app.example.com;

    root  /var/www/app/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    # Hashed build assets: cache hard. They have a content hash in the name,
    # so a new deploy ships new filenames and never serves stale code.
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    # Never cache the HTML entry point, or users get a stale app shell.
    location = /index.html {
        add_header Cache-Control "no-cache";
    }
}

The important parts:

If your API is on the same domain, put it in its own location /api/ { proxy_pass … } above the SPA fallback so API calls don’t get the HTML shell.

sudo nginx -t && sudo nginx -s reload

← All snippets