The Right Way to Handle 404s in Angular SPAs with Nginx


Modern Angular and other single-page applications (SPAs) often break one of the web’s oldest conventions — returning the correct HTTP status code.

When every route, valid or not, serves index.html, even a nonexistent page returns 200 OK.

This confuses search engines and creates “soft 404” issues that hurt your site’s SEO.

This article walks through how to correctly configure Nginx to serve Angular SPAs with real 404 responses, without breaking client-side routing.

You’ll learn how to map valid routes, handle unknown paths gracefully, and still show Angular’s 404 component — all while keeping accurate HTTP responses for crawlers and analytics tools.

By the end, you’ll have a clean, production-ready setup where /nonexistent returns a true 404 Not Found, and Angular displays its own custom 404 view.




The Problem

By default, SPAs rely on client-side routing.
That means the web server always serves index.html, regardless of the URL.

For example:

  • / → Home page ✅
  • /about → About page ✅
  • /random-nonexistent-page → Angular’s 404 component ✅

It works fine for users, but there’s a hidden issue — the HTTP response is always 200 OK, even for routes that don’t exist.

GET /random-nonexistent-page
HTTP/1.1 200 OK
Enter fullscreen mode

Exit fullscreen mode

Search engines interpret this as a soft 404, meaning the page doesn’t exist but the server claims it does.
This can confuse crawlers, waste crawl budget, and reduce your site’s ranking quality.




The Goal

We want Nginx to handle SPAs correctly by:

  1. Serving Angular normally for valid routes with HTTP 200.
  2. Returning HTTP 404 for invalid routes, while still showing Angular’s 404 component.
  3. Keeping static assets and caching behavior unchanged.



Step 1: Define Valid Routes in Nginx

We can define which routes are valid using Nginx’s map directive.
This creates a variable that indicates whether a request path is known or not.

map $request_uri $is_valid_route {
    default 0;
    / 1;
    /products 1;
    /profile 1;
    ~^/orders/[0-9]+$ 1; # Dynamic route
}
Enter fullscreen mode

Exit fullscreen mode

  • 1 → valid route (serve SPA and return 200)
  • 0 → invalid route (return 404)

Every time you add a new route to Angular, update this list to match.
For dynamic routes, you can use regular expressions.




Step 2: Use $is_valid_route in the Main Location Block

Next, we use that variable to control how requests are handled:

location / {
    if ($is_valid_route = 0) {
        return 404;
    }
    try_files $uri $uri/ /index.html;
}
Enter fullscreen mode

Exit fullscreen mode

This tells Nginx:

  • If the route isn’t valid, return 404 immediately.
  • Otherwise, serve the requested file or fall back to index.html for Angular routing.

This ensures that only valid routes return 200 OK.




Step 3: Serve Angular’s 404 Component with a Real 404 Status

The challenge is that we don’t want to show Nginx’s default “Not Found” page.
We still want Angular’s 404 component to appear — but the HTTP response must stay 404.

To do this, use the following configuration:

error_page 404 /index.html;

location = /index.html {
    root /usr/share/nginx/html/customer;
    expires -1;
    add_header Pragma "no-cache";
    add_header Cache-Control "no-store, no-cache, must-revalidate";
}
Enter fullscreen mode

Exit fullscreen mode

Here’s what happens:

  1. When Nginx encounters a 404, it internally rewrites the request to index.html.
  2. Angular bootstraps as usual and uses the current URL to decide which route to show.
  3. The server response remains HTTP 404, not 200. This is the key: Angular still renders the 404 page, but Nginx reports the truth.

This way, users see your app’s 404 view, and crawlers see the correct status code.




Step 4: The Final Result

URL HTTP Status Angular View
/ 200 Home
/products 200 Products
/orders/123 200 Order details
/nonexistent 404 Angular 404 page

The routing experience remains seamless for users, and your SEO health stays intact.




Step 5: Practical Tips

  • Always update the Nginx route map when you introduce new Angular routes.

  • Use regex for dynamic paths (e.g., /orders/:id).

  • Test responses locally before deploying:

  curl -I http://localhost/nonexistent
Enter fullscreen mode

Exit fullscreen mode

You should see:

  HTTP/1.1 404 Not Found
Enter fullscreen mode

Exit fullscreen mode

  • Confirm that your app’s 404 component is configured for Angular’s wildcard route (path: '**').



Conclusion

This setup provides a clean balance between user experience and technical correctness.

You get:

  • Accurate HTTP responses for crawlers and analytics
  • Consistent routing behavior for users
  • Full control over caching and assets

With $is_valid_route and error_page 404 /index.html, your Angular SPA stays SEO-friendly, predictable, and production-ready — all without the complexity of SSR.



Have you handled SPA 404s differently?

Share your setup or tweaks in the comments — I’d love to compare notes!



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *