0

Problem

After a deploy, a subset of users can’t load the app. For them, a specific old chunk URL (e.g. chunk-B3EQFA6E.js) returns index.html with Content-Type: text/html instead of JavaScript. That triggers a MIME error and the app fails to boot.

For me (and most users) the same URL correctly serves application/javascript, and the app works.

Actual error (from console):

Error: Unsupported Content-Type "text/html" loading https://domain.app/chunk-B3EQFA6E.js imported from https://domain.app/_angular_material_expansion.6sH6WbSzQn.js. Modules must be served with a valid MIME type like application/javascript.
        at On (polyfills-0X0I2Qx.js:15:314)
        at async polyfills-0X0I2Qx.js:15:695

Environment:

  • Angular app with micro-frontends (@angular-architects/native-federation)
  • Nginx serving the SPA
  • We previously had a service worker, but we unregistered it on all clients (and verified there’s no SW registered in DevTools for affected users).

Nginx config (relevant parts):

server {
  listen 80;
  root /usr/share/nginx/html;

  # index.html - no cache
  location = /index.html {
    add_header Cache-Control "no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0";
    add_header Pragma "no-cache";
    expires -1;
    try_files $uri =404;   # <-- important
  }

  # SPA fallback (all routes go to index.html)
  location / {
    try_files $uri $uri/ /index.html =404;
  }

  # Remote entry (no cache)
  location ~ ^/remoteEntry.json$ {
    add_header Cache-Control "no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0";
    add_header Pragma "no-cache";
    expires -1;
    try_files $uri =404;
  }
}

Here’s what the DevTools Network → Headers screenshot shows for the failing request:

    •   Request URL: https://<domain>/chunk-B3EQFA6E.js
    •   Request Method: GET
    •   Status Code: 200 OK (from disk cache)
    •   Remote Address: <server-ip>:443
    •   Referrer Policy: strict-origin-when-cross-origin

Response Headers
    •   Content-Type: text/html
    •   Date: Thu, 16 Oct 2025 08:54:20 GMT
    •   ETag: W/"68eff171-c64e"
    •   Last-Modified: Wed, 15 Oct 2025 19:09:37 GMT
    •   Server: nginx/1.29.2
    •   Vary: Accept-Encoding
    •   X-Content-Encoding-Over-Network: gzip

Request Headers (partial; banner shows provisional headers)
    •   Referrer: https://<domain>/dashboard
    •   User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
    •   Sec-CH-UA: "Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"
    •   Sec-CH-UA-Platform: "Windows"
    •   Sec-CH-UA-Mobile: ?0

Key takeaway from the image: the JavaScript chunk is being served as text/html from disk cache, which explains the MIME-type error and why the app breaks for those users.

Questions:

  1. Is the SPA fallback (try_files $uri $uri/ /index.html) the reason an old missing *.js gets served as index.html for some users?
    Should I disable SPA fallback for static assets and return 404 instead, e.g.:

    location ~* \.(js|mjs|css|map|json|png|jpg|gif|svg|ico|woff2?)$ {
        try_files $uri =404;
    }
    

    so missing hashed assets never return HTML?

  2. Any other headers or Nginx rules I should add to make this robust (e.g., Cache-Control: no-store on index.html, long-cache & immutable on static assets, ensuring correct types for .js/.mjs`)?

  3. Recommended way to reproduce this on any other stage so I can test the fix?

2 Answers 2

0

Most likely you receive wrong type happends because of SPA fallback. But that is not the thing that is working wrong here.

Tt seems something is cached. Maybe serviceworker is involved, but it could be just a cache headers.

For example microfrontend1->remoteEntry.js is cached, and it contains OLD links to chunk-OLDHASH.js. Then the request for chunk-OLDHASH.js is made and nginx fallbacks to index.html.

I would recommend to add no-cache for these files that aren't changing names during redeployment.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the suggestion. Could you clarify which files you mean by “files that don’t change names during redeployment”? We already set Cache-Control: no-cache, no-store, must-revalidate on remoteEntry.json, and I can confirm in the Network tab it’s always fetched fresh.
0

Wouldn't the simplest solution be to clear the browser caches of the affected clients?

Meanwhile, answering your main question:

Is the SPA fallback (try_files $uri $uri/ /index.html) the reason an old missing *.js gets served as index.html for some users?

You don't have that kind of callback in your nginx configuration. What you have is 404 Not Found status code as the fallback parameter:

# SPA fallback (all routes go to index.html)
location / {
  try_files $uri $uri/ /index.html =404;
}

# index.html - no cache
location = /index.html {
  ... caching here
  try_files $uri =404;
}

A processed request can reach the second location only if the index.html file is requested explicitly or via the internal redirect performed by the implicit index directive in case of the root (GET /) request. For any other non-existent file, the request will never escape the first location. After the first two checks fail, the third check (for the index.html file in the root directory) will succeed, or if for some reason there is no index file, the 404 Not Found HTTP error will be returned. If the third check will succeed, request will be served within the first location, since only the last try_files directive parameter defines the fallback action. Many people don't pay attention to the fact that the last parameter of the try_files directive has a completely different meaning compared to all the others, as shown by this directive definition in the documentation:

Syntax:

try_files file ... uri;
try_files file ... =code;

You need to use the proper fallback (/index.html), and you don't need to put the try_files directive anywhere except in the location where the fallback is needed (and even there, there is a way to avoid it, though that is beyond the scope of your question). Your configuration should look as follows:

server {
  listen 80;
  root /usr/share/nginx/html;

  # SPA fallback (all routes go to index.html)
  location / {
    try_files $uri /index.html;
  }

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

  # Remote entry (no cache)
  location = /remoteEntry.json {
    add_header Cache-Control ...
  }
}

This way any request for any non-existent file will be served within the second location, with the proper caching headers added.

5 Comments

Clearing the browser cache didn’t resolve the issue. Regarding the SPA fallback, I was trying to insinuate, should static asset requests return 404 instead of index.html? For example: location ~* \.(?:js|mjs|css|map|json|png|jpe?g|gif|svg|ico|woff2?)$ { try_files $uri =404; }
Honestly, I don't understand how cleaning the browser cache may not resolve the issue (at least if no Service Worker is involved). Regarding your second question, it seems unnecessary to me; the index.html file should no longer be cached by any means. However, even if you decide to do this, stop using the try_files directive where it is not necessary: location ~* \.(?:...)$ {} would be completely enough (moreover, slightly more performant).
Thank you for your answer, even if clearing browser cache did work, how does that prevent the issue from happening again? How does removing try_files directive where it is not necessary help with the issue in question?
Did you read the answer carefully? What should preventing the issue from happening again is changing the try_files directive fallback action: try_files $uri $uri/ /index.html =404try_files $uri /index.html. This way any request for any non-existent file will be served within the location = /index.html { ... }, where the proper caching headers are defined. With your original configuration such requests were served within the root location, where no caching headers were defined, and I tried my best to explain why is it so.
Removing other try_files directives does not related to the original issue; they are redundant and slightly degrade the overall configuration performance, see serverfault.com/a/1193428/498657

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.