How to Configure Nginx for Single Page Applications (SPA Routing)

Single Page Applications (React, Vue, Angular) handle routing client-side in JavaScript — but the web server still needs specific configuration to support this correctly, or users will hit 404 errors on page refresh or direct URL access.

The Problem SPA Routing Configuration Solves

When a user navigates within a React/Vue app, JavaScript handles the URL change without a real page reload. But if they refresh the page or bookmark a URL like /dashboard/settings, the browser makes a real HTTP request for that exact path — which doesn't exist as an actual file on the server, since it's a client-side route.

The Solution: Fallback to index.html

server {
    listen 80;
    server_name myapp.example.com;
    root /var/www/myapp/dist;
    index index.html;

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

try_files attempts to serve the exact requested file, then a directory, and finally falls back to index.html — letting the client-side JavaScript router take over and render the correct view based on the URL.

Preventing the Fallback from Applying to Real Assets

Static assets should still 404 correctly if they genuinely don't exist, rather than silently serving index.html:

location /assets/ {
    try_files $uri =404;
}

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

Caching Static Assets with Long Expiration

location ~* \.(js|css|woff2|png|jpg|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Modern build tools (Vite, Webpack, Create React App) generate content-hashed filenames, making aggressive caching safe — a new deployment produces new filenames automatically.

Never Cache index.html Aggressively

location = /index.html {
    add_header Cache-Control "no-cache";
}

Since index.html references the current build's hashed asset filenames, it must always be fetched fresh, or users could end up loading a stale HTML file referencing assets that no longer exist after a deployment.

Handling API Requests Alongside the SPA

location /api/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
}

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

Nginx matches the most specific location block first, so /api/ requests correctly route to your backend while everything else falls through to the SPA.

Setting Up Client-Side Routing for React Router

No special React-specific configuration is needed beyond the Nginx try_files fallback — React Router (and equivalent libraries for Vue/Angular) handles the rest once the correct HTML is served.

Deploying an SPA Build

npm run build
rsync -avz --delete ./dist/ deploy@YOUR_SERVER_IP:/var/www/myapp/dist/

Common Errors

404 on page refresh for any route except the homepage — the try_files fallback to index.html is missing; add it as shown above.

Stale content after deployment — verify index.html specifically isn't being cached aggressively, even if other assets are.

API requests returning the SPA's HTML instead of JSON — the API location block isn't matching correctly, or is defined after (rather than before) the catch-all SPA fallback in a way that causes precedence issues.

Best Practices

  • Always use try_files with fallback to index.html for any client-side-routed SPA
  • Cache hashed assets aggressively, but never index.html itself
  • Place API proxy rules with sufficient specificity to avoid conflicting with the SPA fallback

Continue Reading

Browse more articles in Static Site Hosting & Frontend Deployment.

  • spa routing, nginx try_files, react router nginx, single page application
  • 0 משתמשים שמצאו מאמר זה מועיל
?האם התשובה שקיבלתם הייתה מועילה

מאמרים קשורים

How to Host a Static Website on a VPS with Nginx

Static websites — plain HTML, CSS, and JavaScript with no server-side processing —...

How to Deploy a Next.js Application on a VPS

Next.js supports several deployment modes — fully static export, server-side rendering with...

How to Deploy a Static Site Built with Astro, Hugo, or Jekyll

Static site generators (Astro, Hugo, Jekyll) produce plain HTML/CSS/JS at build time —...

How to Optimize Images for Web Performance

Images are typically the largest contributor to page weight and load time. This guide covers...

How to Set Up a Jamstack Site with a Headless CMS Backend

The Jamstack architecture (JavaScript, APIs, Markup) combines a pre-built static frontend with a...