Cache Busting in Angular

Nil Seri
Stackademic
Published in
4 min readNov 26, 2023

--

Cache Busting and Lazy Loading preloadingStrategy

Photo by Surfing Croyde Bay on Unsplash

What is Cache Busting?

If you make an update to your site, since the cached version of the file is stored in your visitors’ browsers, they may be unable to see the changes made. This is due to the fact that a visitor’s browser will locally store a cached copy of your static assets. Cache busting is used to force the browser to load the most recent version of a file, rather than a previously cached version.

You may have “outputHashing”: “all” in your angular.json file as it is enabled by default but the problem is that the client’s browser is caching index.html file and thus it is not aware of the new chunks.

Cache Related Headers

Cache-Control HTTP header fields:

  • no-cache : (response directive) If you want caches to always check for content updates while reusing stored content, this is the directive to use. It does this by requiring caches to revalidate each request with the origin server.
  • no-store : (response directive) indicates that any caches of any kind (private or shared) should not store this response.
  • must-revalidate : (response directive) once the cache expires don’t serve the stale resource. ask the server and revalidate. indicates that the response can be stored in caches and can be reused while fresh. If the response becomes stale, it must be validated with the origin server before reuse. HTTP allows caches to reuse stale responses when they are disconnected from the origin server. must-revalidate is a way to prevent this from happening

You can read more details about Cache-Control headers here.

Pragma HTTP header field:

  • no-cache : Same as Cache-Control: no-cache. Forces caches to submit the request to the origin server for validation before a cached copy is released.

You may see this header being added as well as the others above but it is no longer recommended and it is deprecated. You can read more details about Pragma header here.

Expires HTTP header field:

  • The Expires HTTP header contains the date/time after which the response is considered expired. Invalid expiration dates with value 0 represent a date in the past and mean that the resource is already expired. You may come across “Expires” value as -1 but this is an invalid usage.

You can read more details about Expire header here.

Implement Cache Busting

To provide this, you need to add specific headers to both your index.html and your Nginx or Apache server configuration. Be sure to perform both additions; only adding them to index.html will not help.

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Expires" content="0" />

For Nginx, config such as written below should be added to /etc/nginx/conf.d/default.conf file content:

server {
// ...
location / {
try_files $uri $uri/ /index.html =404;
expires 0;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
// ...
}

Apache config with SetEnvIf directive (which defines environment variables based on attributes of the request):

SetEnvIf REQUEST_URI ^/$ DISABLE_CACHE_CONTROL
Header set Cache-Control "no-cache, no-store, max-age=0, must-revalidate" env=DISABLE_CACHE_CONTROL

After these updates, users won’t have to Ctrl + F5 after every deployment to get the updates you did to your website.

Lazy Loading Issues

As Angular generates a SPA (Single Page Application), NgModules are eagerly loaded by default. Lazy loading is a technology of angular that allows you to load JavaScript components when a specific route is activated. It improves application load time speed by splitting the application into many bundles, which reduces load times. When the user navigates by the app, bundles are loaded as needed.

If you have implemented lazy loading in your application, users may sometimes come across loading chunk errors as they struggle to navigate within the website:

Error: Uncaught (in promise): Error: Loading chunk 0 failed.

This happens when a user has stayed on a module without navigating and you deploy a newer version that will affect the chunk (hash code) of the lazy loaded module the user wants to navigate next so the browser is not able to download it from the server.

You can write a GlobalErrorHandler that will run “document.location.reload()” whenever chunk error appears, such as:

import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { CommonModalComponent } from '@shared/components/modal/common-modal/common-modal.component';
import { BsModalService } from 'ngx-bootstrap/modal';

@Injectable()
export class LoadChunkErrorHandler implements ErrorHandler {

constructor(private readonly injector: Injector) { }

handleError(error: any): void {
const chunkFailedMessage = /Loading chunk [\d]+ failed/;
if (chunkFailedMessage.test(error.message)) {
console.error(error);
this.openNewVersionBanner();
}
}

openNewVersionBanner(): void {
const initialState = {
modalTitleText: 'NEW_VERSION_AVAILABLE_TITLE',
modalBodyTextKey: 'NEW_VERSION_AVAILABLE_BODY'
};
const modalService = this.injector.get(BsModalService);
const bannerModal = modalService.show(CommonModalComponent, { class: 'modal-dialog-centered modal-medium', initialState, ignoreBackdropClick: true });
bannerModal.content.onClose.subscribe(() => window.location.reload());
}
}

Also, add your custom error handler to app.module’s providers list:

{ provide: ErrorHandler, useClass: LoadChunkErrorHandler },

Another solution would be to use preloadingStrategy with value “PreloadAllModules”:

RouterModule.forRoot([], {
preloadingStrategy: PreloadAllModules
}),

Preloading improves UX by loading parts of your application in the background. By doing this, users don’t have to wait for the chunks to download when they activate a route and thus helps to prevent loading chunk errors.

Happy Coding!

--

--

I would love to change the world, but they won’t give me the source code | coding 👩🏻‍💻 | coffee ☕️ | jazz 🎷 | anime 🐲 | books 📚 | drawing 🎨