8

I thought I have been successfully cache busting with angular-cli's --output-hashing option set to all to ensure users get the latest chunks/updates. I realized this isn't always working for lazily-loaded modules.

If I make changes only to a lazily-loaded module + build + deploy to IIS, AND go to a URL that belongs to the lazily-loaded module, then I get the latest changes as expected.

However, if I close and re-open the browser and go to a URL that does not belong to the lazily-loaded module, and then click to a link that takes me to the lazily-loaded module, the latest changes are not observed.

It's almost as if browser is assuming nothing is changed whenever I enter the app from a module that hasn't changed, and doesn't pick up the new chunk later when I visit the module that is changed.

Even stranger, when I tried to replicate the issue to provide some screenshots, the issue went away.

Has anyone else experienced inconsistency with cache busting using output-hashing?

Edit:

After investigating some more, it turns out that if I go to the landing page of my site [IP address]:[port], then the index.html comes from the cache.

Instead, if I go to any other route [IP address]:[port]/[route], then the index.html comes from the server.

Since index.html points to inline.(hash).bundle.js, which in return points to lazily-loaded modules, getting the old copy of index.html results in getting old versions of inline.js and other modules.

I tried to add <meta http-equiv="expires" content="0" /> to index.html and cleared browser cache, and still got the file from cache.

I also tried adding a section into web.config, but this did not work either.

How to ensure index.html always comes from the server?

enter image description here

Uğur Dinç
  • 2,415
  • 1
  • 18
  • 25
  • How are you serving up your Angular app? Static index.html file or served up from some server-side language (e.g. .net mvc, node.js...). – Daniel W Strimpel Mar 13 '18 at 23:57
  • @DanielWStrimpel The stand-alone approach. – Uğur Dinç Mar 14 '18 at 00:15
  • I wonder if the problem might be related to the index.html file not changing and being cached by the user. I say that because if none of the non-lazy loaded modules were changed their hashes wouldn't have changed, thus their names wouldn't have changed in the index.html file. Sorry, I'm just grasping at straws to help you as I've never experienced this problem (my use cases are on IIS w/ .net MVC). – Daniel W Strimpel Mar 14 '18 at 00:23
  • @DanielWStrimpel Updated the question. – Uğur Dinç Mar 14 '18 at 20:14
  • 2
    Can you try to set the `Cache-Control` header to this: `no-cache, no-store, must-revalidate` – Daniel W Strimpel Mar 14 '18 at 21:15
  • @DanielWStrimpel That worked! Thank you! Please edit the answer and I will select. – Uğur Dinç Mar 14 '18 at 21:19
  • I think you might have hit https://github.com/angular/angular/issues/28114#issuecomment-654779113, although you should have an error in console. There is now an 'uncoverable' observable to subscribe to when the service worker cannot recover a state (for instance, when the lazy module it tries to load from an outdated index does not exist on the server). – cghislai Mar 29 '21 at 14:31

2 Answers2

11

Your answer can be found in this SO related question, which outlines how to set up IIS to set the cache settings for the index.html file: How to disable caching of single page application HTML file served through IIS?

Additionally based on the Mozilla Docs, you should set the Cache-Control header to this: no-cache, no-store, must-revalidate

As for the cause of this issue, when Angular is generating the output bundle, it will create JS filenames that will uniquely identify the current version of that file if told to use output hashing (i.e. they create a hash of the file contents and include this in the filename so that browsers do not serve up the cached version of these updated files: js-filename.[hash of content].js instead of just js-filename.js). The problem with this is that the browsers will also serve up the cached version of the index.html file that Angular generates, which will point to the old version of the JS files.

To resolve this issue, you must set up your web server to set the appropriate Cache-Control setting (outlined above) on the index.html file so that the browser never caches this file.

Daniel W Strimpel
  • 8,190
  • 2
  • 28
  • 38
  • The selected answer from that question doesn't seem to work for my case. Not sure why. – Uğur Dinç Mar 14 '18 at 20:51
  • Can you update the question with details about what headers are being sent back with the request? – Daniel W Strimpel Mar 14 '18 at 20:56
  • I could do this, and I actually configured IIS's response headers to send back "no-cache", but since the browser is not even making an initial request to the server, I believe they are getting ignored. I figured clearing the cache first should get the updated index.html file + new response header, and it does, but subsequent requests still come from "disk cache" despite the response header containing "Cache-Control:no-cache" – Uğur Dinç Mar 14 '18 at 21:02
  • Updated the question. – Uğur Dinç Mar 14 '18 at 21:09
  • Won't these headers cause the files never to be cached? This doesn't seem to make sense? Or are you saying that the problem is because the HTML page is being cached so it still has references to the old bundles? It'd be helpful to include this information in the answer, right now I have to read through comments to try to grasp what this answer means and it's still unclear. – Ruan Mendes Feb 27 '20 at 15:43
  • @JuanMendes yes that would cause all files to never be cached if it was set for all files. However in this case we are specifically talking about setting the `Cache-Control` for only the `index.html` file so that it is never cached (since it will point to the version of the JS files). I didn't want to duplicate the content in that SO question so I just linked to it and added additional information that was recommended from Mozilla. – Daniel W Strimpel Feb 27 '20 at 16:02
  • My ask was that you edit the question to contain the fact that you should never cache your HTML file since you don't control its name. Right now your answer is only targeting the OP but adding full context to your answer will make it more useful to others. – Ruan Mendes Feb 27 '20 at 16:06
  • 1
    @JuanMendes thanks for the suggestion to add this context to be more useful. – Daniel W Strimpel Feb 27 '20 at 16:20
  • angular service worker is actually polling the ngsw.json file in order to notice when the app (including index.html and all bundles) should be reloaded. index.html is intended to be cached by the SW for offline rendering. Read official documentation https://angular.io/guide/service-worker-getting-started – cghislai Mar 29 '21 at 14:48
0

yes, angular cli output hashing is always supposed to work with lazy loaded modules.

If angular tries to load a lazy module that does not exists on the server anymore, then it probably loaded an outdated index.html. There can be a couple of reasons for that:

  • An error occured during the service worker update, leaving the app in an unrecoverable state. The SwUpdate service now have an unrecoverable property which is an Observable to subscribe to in order to handle those cases (usually, reloading the document will reload the app correctly).
  • The ngsw.json file polled by the angular sw is cached between the client and the server, or has not been updated correctly during the build process.

For more information, refer to angular documentation: https://angular.io/guide/service-worker-intro

For the lazy module loading issue, refer to https://github.com/angular/angular/issues/28114

cghislai
  • 1,751
  • 15
  • 29