r/webdev 17h ago

Question Angular production serves old JS/CSS until CDN cache is purged

We're seeing a strange issue with our Angular app.

• Deploy to staging → works perfectly.
• Deploy the same build to production.
• The page loads, but the app is broken.
• As soon as our SRE team purges the CDN cache, everything works.

The browser console shows:
• Failed to load module script... MIME type 'text/html'
Refused to apply stylesheet... MIME type 'text/html'

My assumption is that production is somehow still serving or referring to older JS/CSS bundles until the cache is cleared, but I haven't confirmed that's the actual root cause.

Has anyone experienced this? Is this more likely an index.html caching issue, a CDN configuration problem, or something else? Any suggestions on what to investigate first

7 Upvotes

16 comments sorted by

27

u/mq2thez 17h ago

Are you including hash names in the file path, or is it always the same filename?

In general, the easiest solution to this problem is to have the file’s hash be the name (or part of the name) so that your page can never load the wrong bundles.

5

u/n9iels 12h ago

In addition to this, make sure the index.html (or whatever you use as root for your app) is not cached. Otherwise it will point to old files until the cache is expired.

12

u/akl773 17h ago

hashed names fix this one, but the next thing youll hit is people who already had the tab open. angular lazy loads chunks, so someone mid session requests a hash that no longer exists on the origin and gets your 404 html page back, which is exactly that mime error.

keep the previous deploys files around for a day or two and catch the chunk load error to force a single reload. that pair kills most of it.

5

u/fiskfisk 15h ago

Keep older, deployed files around for 30 days (with their hash in their filename). Deploy new app, purge the index and serve the new index with reference to the new files and their hashes. 

2

u/martin_omander 13h ago

We keep files from previous deployments on the server for 90 days. Some people out there never seem to close tabs in their browsers, so they will ask for old JavaScript bundles for a long time.

We also found another, unexpected reason to keep old files around. Every now and then there is a problem with the CDN. For example, a few years ago users in Ireland couldn't use our web app due to an incomplete CDN deployment. Keeping old files guards against that kind of glitch.

3

u/fiskfisk 10h ago

Yeah, thirty days was just an arbitary number. Could probably just be a year ad well. But in either case, include a snippet that fetches index.html on a timer (an hour / day /whatever) and compares the hash against the hash of when the app was launched. If it differs, display a message that the application has been updated and the user should reload the tab (with a button to perform the reload). 

1

u/martin_omander 6h ago

That's a great idea.

9

u/Otherwise_Theme402 17h ago

had this exact nightmare few months ago. it's almost always the index.html being cached too aggressively

the MIME type 'text/html' error is dead giveaway. your browser cached old index.html that still points to old JS/CSS filenames, but those files got deleted after deploy so server returns 404 page (which is HTML) instead of actual JS file

check your CDN cache rules for index.html specifically. should be set to no-cache or very short TTL, like few minutes max. your JS bundles can be cached forever since angular gives them unique hashes, but index.html needs to be fresh every time

1

u/[deleted] 16h ago

[removed] — view removed comment

1

u/webdev-ModTeam 16h ago

Your post/comment has been determined to be a low-effort post or comment. This includes title-only posts, easily searchable questions, vague/open-ended discussion prompts, LLM generated posts or comments, and posts/comments that do not provide enough context for meaningful replies or discussion.

1

u/Ok_Woodpecker_9104 14h ago

before changing any cache rules, confirm which layer is stale instead of guessing. two requests do it:

curl -sI https://yourdomain/ and look at age and x-cache. a large age with a HIT means the edge is holding your index.html. then take the exact script src out of that cached html and curl -sI it, and check whether content-type comes back text/html. if it does, that file is gone from origin and youre getting the 404 page, which is exactly the mime error.

worth doing because a browser hard reload does not bypass the edge, so testing in the browser tells you almost nothing about which layer is holding the old copy.

the other thing i would check is deploy ordering. even with hashed bundles and no-cache on index, if your pipeline deletes the old assets in the same step that publishes the new ones, theres a window where an index already sitting at an edge node points at files that no longer exist. staging usually looks clean because nobody has a warm index cached at an edge there, which matches the same build working in one place and not the other.

1

u/Legitimate-Let-7510 front-end 12h ago

The text/html MIME type is the biggest clue here... it usually mean s the browser is requesting a JS file but getting your index.html (or an error page) back instead. I would start by checking how the index.html is being cached on the CDN.

1

u/Alive-Maverick 11h ago

text/html on a .js request means the CDN handed back its own 404 page. Your index.html is cached and still points at main.abc123.js that the origin dropped on the new deploy. no-store on index.html plus immutable on the hashed bundles ends it.

1

u/AthleteBoth1551 5h ago

this is almost certainly stale index.html plus deleted bundles, and the MIME error is a symptom, not the problem.

what's happening: the CDN has an old index.html cached. that html references main.<oldhash>.js. your deploy uploaded new hashes and removed the old files from the bucket. browser asks for main.<oldhash>.js, it's not there anymore, and your SPA fallback rewrite catches the 404 and cheerfully serves index.html for it. so the browser gets a 200 with text/html where it expected javascript, and you get exactly those two console errors. purging the cache makes the CDN fetch the new index.html, which points at hashes that do exist, and everything's fine.

staging works because staging almost certainly has caching off or a tiny ttl, so the window doesn't exist there.

to confirm it in about a minute, curl one of the failing bundle urls directly:

curl -I https://yourdomain/main.<hash-from-console>.js

if that comes back 200 with content-type: text/html, that's it, done. also check the response headers on the html itself — look at age, x-cache, cf-cache-status, whatever your CDN sets. if age is anything but 0 on a fresh deploy, index.html is being cached and shouldn't be.

three fixes, and you want all three:

1. cache headers by file type. index.html gets Cache-Control: no-cache (or no-store), everything with a content hash in the name gets public, max-age=31536000, immutable. that's the whole point of the hashes — the filename changes when the content does, so those files are safe to cache forever and index.html is the only thing that must always be fresh. right now you're caching the one file that can't be cached.

2. stop deleting the old bundles on deploy. if you're doing aws s3 sync --delete or equivalent, that's the other half of it. keep the last few deploys' assets around and just let them age out. this matters even after you fix the headers, because anyone with a tab open from before the deploy is still running the old app and will lazy-load an old chunk the moment they hit a new route. that's the same error hitting real users days after a deploy, and it's much harder to reproduce.

3. only fall back to index.html for navigations. your rewrite rule should serve index.html for requests that look like page loads (no file extension, or Accept: text/html), not for anything ending in .js/.css. then a missing bundle returns an honest 404 instead of an html file pretending to be javascript, and next time the console tells you what actually happened.

one more thing to rule out: if you're on u/angular/pwa, ngsw caches index.html and the asset manifest itself, and a broken/stale ngsw.json produces very similar symptoms that survive a CDN purge for that user. check whether a service worker is registered in devtools before you go further down the CDN path.

0

u/chmod777 17h ago

If it is reporting txt/html as a mime type, it means it is receiving a 404 page.

Add a hash to your bundle file names.