Cache Busting: 3 Ways To Fix Stale Deployment in React

Fateh Ali
14 min readDec 30, 2023

--

As updates roll in with increasing frequency, applications need to embrace efficient caching strategies to ensure users can easily access the latest versions without encountering temporary storage issues.

This is something I recently encountered. I received complaints from users who hadn’t received the most recent update upon its release.

Upon closer inspection, the issue stemmed from the users’ browser cache.

While the solution might seem straightforward — clear the cache — it’s far from ideal, as users would need to perform this action every time an update is released.

This inconvenience prompted me to develop a more effective caching strategy.

During exploration of developing a caching strategy, I came across a technique called cache busting.

This technique is used to force the browser to download the latest file version by changing its unique identifier, usually by adding query parameters or modifying the file name.

In this post, I want to share my experience in fixing this issue. If you’re facing a similar situation, I hope this information can help you.

Understanding Browser

When I first started developing the app, I never really gave much thought to the importance of caching. Dealing with stale deployment issues got me scratching my head, wondering what caused it and how to fix it.

So, a little insight into how browser caching works might just be the key to tackling this issue more effectively.

How Caching Works

The behavior of web browsers, including their caching mechanisms, is governed by standards and web specifications outlined by various organizations. Two important specification related to caching are HTTP protocol specified by the Internet Engineering Task Force (IETF).

They define how caching should be handled by web clients and servers such as Expires, Cache-Control, Last-Modified, and ETag that can be used to control caching behavior.

  • The Expires This is the initial version of the HTTP/1.0 protocol and includes some headers like Expires. It was first released in 1996. HTTP header contains the date/time after which the response is considered expired.
  • The Cache-Control This is a newer version and is widely used today. It introduced the Cache-Control header as a more flexible way to control caching. It was first released in 1997. HTTP header field holds directives (instructions) — in both requests and responses — that control caching in browsers and shared caches (e.g. Proxies, CDNs).
  • The Last-Modified response HTTP header contains a date and time when the origin server believes the resource was last modified. It is used as a validator to determine if the resource is the same as the previously stored one.
  • The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.

Why Caching

Browsers store cached web pages and resources is not solely dictated by rules or specifications set by organizations but is also driven by performance and user experience considerations.

Caching involves storing copies of files locally on the user’s device so that subsequent visits to the website or page reloads can be faster.

View List Cached Sites

As a developer, you might be wondering where to find the cache and you might find yourself doing what I usually do — opening the DevTools (F12).
In Firefox: F12 > Storage > Cache Storage.

Firefox Cache Storage
Firefox Cache DevTool

In Chrome: F12 > Application > Cache Storage.

Chromium Cache Storage
Chrome Cache DevTool

However, it turns out you didn’t find it. Caches that are automatically cached by the browser by default, such as caches for images, stylesheets, scripts and other elements of web pages, may not be specifically visible in this Cache Storage.

To view a list of sites cached by default in Firefox type about:cache in the URL.

Alternatively, you can go to settings or type about:preferences#privacy > Manage Data. This is where the sites that are cached by default, including your React application, are located.

Unfortunately, within Chrome itself, I couldn’t find the same feature. For older versions, you might still be able to access it by typing chrome://cache.

Setup

Prerequisite

For the sake of simplicity, I’ll be implementing this using create-react-app with its default template. But I believe this can be applied to other CLI tools or their derivatives, such as create-next-app, or even different frameworks or stacks, because the basic concepts remain the same.

From the web server perspective, in this write-up, I’ll be using Nginx as the web server. However, you can certainly opt for other web servers like Apache if that’s more in line with your preferences.

On the bundler side of things, since we’re working with CRA, the bundler we’ll be using is automatically set to webpack.

CLI Tool: create-react-app
Bundler: Webpack
Web Server: Nginx

Installation

Go to your desired folder or directory and run:

# npm
npx install create-react-app cache-busting

# yarn
yarn create react-app cache-busting

# pnpm
pnpx install create-react-app cache-busting

By the way, since create-react-app itself builds a setup with no configuration, instead of ejecting from react-scripts, we’ll need one more tool in our toolkit — react-app-rewired for tweaking the webpack config. Feel free to go with craco or any other tool of your liking.

Once the installation is complete run

# npm
npm install --save-dev react-app-rewired

# yarn
yarn add --dev react-app-rewired

# pnpm
pnpm -D install react-app-rewired

Create a config-overrides.js file in the root directory

/* config-overrides.js */

module.exports = {
paths: (paths, env) => {
console.log("> paths_stage\n")
return paths;
},
jest: config => {
console.log("> jest_stage\n")
return config;
},
devServer: configFunction => {
console.log("> devServer_stage\n")
const config = function(proxy, allowedHost) {
return configFunction(proxy, allowedHost)
}
return config;
},
webpack: (config, env) => {
console.log("> webpack_stage\n")

// Your custom webpack here


return config;
}
}

That file is what we’ll use to customize webpack. If you’re rolling with nextjs or maybe vite as your bundler, make sure to adjust the location accordingly for your custom bundler.

‘Flip’ the existing calls to react-scripts in npm scripts for start, build and test

  /* package.json */

"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}

Build Production

Before we dive into experiment, we need the production-built version. So, make sure you’ve executed the React build command:

# npm
npm build

# yarn
yarn build

# pnpm
pnpm build

This will generate something in the /build folder, and your output structure would resemble the following:

build/
├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── static/
├── css/
│ ├── main.f855e6bc.css
│ └── main.f855e6bc.css.map
├── js/
│ ├── 638.2ef45c58.chunk.js
│ ├── 638.2ef45c58.chunk.js.map
│ ├── main.6204db87.js
│ ├── main.6204db87.js.LICENSE.txt
│ └── main.6204db87.js.map
└── media/
└── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg

Access Log

On the web server side, we’ll try to set up a small tool to provide additional information. It’s optional, so you don’t have to do it if you don’t want to, or perhaps you can simply try cloning it here https://github.com/fatehalive/cache-busting

However, for the sake of easier log reading, we’ll customize the log format a bit to suit our needs.

/* nginx.conf */

log_format custom_log '[$time_local] $request $body_bytes_sent';

server {

# Other Nginx configurations
# ...

access_log /var/log/nginx/custom.log custom_log;
}

We defines a custom log format named custom_log. It specifies the format in which log entries will be recorded.

The above configuration will produce a log format like this:

[23/Dec/2023:00:04:21 +0700] GET / HTTP/1.1 394
[23/Dec/2023:00:04:21 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[23/Dec/2023:00:04:23 +0700] GET /favicon.ico HTTP/1.1 3870
[23/Dec/2023:00:04:23 +0700] GET /logo192.png HTTP/1.1 5347
[23/Dec/2023:00:04:23 +0700] GET /static/js/main.6204db87.js HTTP/1.1 143844
[23/Dec/2023:00:04:25 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[23/Dec/2023:00:09:20 +0700] GET / HTTP/1.1 0
[23/Dec/2023:00:09:20 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[23/Dec/2023:00:09:22 +0700] GET /static/js/main.6204db87.js HTTP/1.1 143844
[23/Dec/2023:00:09:23 +0700] GET /logo192.png HTTP/1.1 5347
[23/Dec/2023:00:09:24 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[23/Dec/2023:00:11:38 +0700] GET / HTTP/1.1 394
[23/Dec/2023:00:11:38 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[23/Dec/2023:00:11:39 +0700] GET /static/js/main.6204db87.js HTTP/1.1 143844
[23/Dec/2023:00:11:46 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632

The logs above will continue to accumulate, making it a bit challenging for us to analyze logs per page reload. Therefore, I’ve created a small script to group logs per minute along with the accumulation of size and the number of resources sent.

#!/bin/bash

# read size log group by minute
awk '
BEGIN { separators[1] = "["; separators[2] = ":" }
{
n = split($1, time_local, separators[1])
for (i in time_local) {
if (separators[i] == ":") {
split(time_local[i], date_time, ":")
}
}
date = date_time[1]; HH = date_time[2]; mm = date_time[3]; ss = date_time[4]
timestamp = date ":" HH ":" mm
bytes_sent = $NF
total_bytes[timestamp] += bytes_sent
count[timestamp]++
logs[timestamp] = logs[timestamp] $0 "\n"
}
END {
for (timestamp in total_bytes) {
total_kb = total_bytes[timestamp] / 1024
printf "%s[%s] Total Size: %.2f KB Count: %d\n\n", logs[timestamp], timestamp, total_kb, count[timestamp]
}
}' "$1"

This script will group the logs above as shown below:

[23/Dec/2023:00:04:21 +0700] GET / HTTP/1.1 394
[23/Dec/2023:00:04:21 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[23/Dec/2023:00:04:23 +0700] GET /favicon.ico HTTP/1.1 3870
[23/Dec/2023:00:04:23 +0700] GET /logo192.png HTTP/1.1 5347
[23/Dec/2023:00:04:23 +0700] GET /static/js/main.6204db87.js HTTP/1.1 143844
[23/Dec/2023:00:04:25 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[23/Dec/2023:00:04] Total Size: 153.19 KB Count: 6

[23/Dec/2023:00:09:20 +0700] GET / HTTP/1.1 0
[23/Dec/2023:00:09:20 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[23/Dec/2023:00:09:22 +0700] GET /static/js/main.6204db87.js HTTP/1.1 143844
[23/Dec/2023:00:09:23 +0700] GET /logo192.png HTTP/1.1 5347
[23/Dec/2023:00:09:24 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[23/Dec/2023:00:09] Total Size: 149.03 KB Count: 5

[23/Dec/2023:00:11:38 +0700] GET / HTTP/1.1 394
[23/Dec/2023:00:11:38 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[23/Dec/2023:00:11:39 +0700] GET /static/js/main.6204db87.js HTTP/1.1 143844
[23/Dec/2023:00:11:46 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[23/Dec/2023:00:11] Total Size: 144.19 KB Count: 4

Now, with all the preparations in place, we will finally proceed to the main part of the article, implementing cache busting in React.

Implementation

As we have understood before, cache busting is a technique that involves changing its unique identifier. Additionally, controlling caching involves several HTTP headers, as mentioned earlier.

Let’s continue with the implementation section, focusing on cache busting using three different methods: via Bundler (Webpack), via Web Server (Nginx), and via a Web Worker.

1. Bundler (Webpack)

build/
├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── static/
├── css/
│ ├── main.f855e6bc.css
│ └── main.f855e6bc.css.map
├── js/
│ ├── 638.2ef45c58.chunk.js
│ ├── 638.2ef45c58.chunk.js.map
│ ├── main.6204db87.js
│ ├── main.6204db87.js.LICENSE.txt
│ └── main.6204db87.js.map
└── media/
└── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg

In React, specifically in Create React App (CRA), this technique is applied by default. When we build, webpack embeds a hash into the output filenames. The above is an example of a hash generated without explicitly creating it. As far as I know, CRA sets the output with [contenthash] short MD-5 by default. However, the generated output may differ if we explicitly define it.

Override or Manually Set

In Webpack, [hash] and [contenthash] are placeholders that can be used in the output.filename configuration to include a unique hash or content-based hash in the output filenames. These placeholders help with cache busting by changing the filename whenever the content of the file changes.

[hash]

/* webpack.config.js */
module.exports = {
// ... (other configurations)
output: {
filename: 'main.[hash].js'
}
}

/* config-overrides.js */
module.exports = {
// ... (other configurations)
webpack: (config, env) => {
console.log("> webpack_stage\n")

// Your custom webpack here
config.output.filename = `static/js/[name].[hash].js`

return config;
}
}

The [hash] placeholder generates a unique hash based on the entire build. If any file in your project changes, the hash for all files will change. It refers to a unique hash for the entire build.

This means that if any file in your project changes, the hash for all files will indeed change.

However, when you changing a little bit of content in a specific file like README.md, it might not trigger a change in the hash if that file is not part of the webpack entry points or dependencies.

{
target: [ 'browserslist' ],
stats: 'errors-warnings',
mode: 'production',
bail: true,
devtool: 'source-map',
entry: '/opt/cache-busting/src/index.js',
output: {
path: '/opt/cache-busting/build',
pathinfo: false,
filename: 'static/js/[name].[contenthash:8].js',
...
},
...
}

This is useful for cache busting in scenarios where multiple files might change together (e.g., when a new dependency is added).

[contenthash]

/* webpack.config.js */
module.exports = {
// ... (other configurations)
output: {
filename: 'main.[contenthash].js'
}
}

/* config-overrides.js */
module.exports = {
// ... (other configurations)
webpack: (config, env) => {
console.log("> webpack_stage\n")

// Your custom webpack here
config.output.filename = `static/js/[name].[contenthash].js`

return config;
}
}

The [contenthash] placeholder generates a hash based on the content of the file. If the content of a specific file changes, only the hash for that file will change.

This is more granular and is often preferred for cache busting, as it allows browsers to cache unchanged files while only fetching those that have been modified.

A little addition, actually, this is just an opinion. You can also add the version number to the file name like this:

/* config-overrides.js */
const path = require('node:path')
const appVersion = require(path.join(process.cwd(), 'package.json')).version;

module.exports = {
// ... (other configurations)
webpack: (config, env) => {
console.log("> webpack_stage\n")
// Your custom webpack here
config.output.filename = `static/js/[name].v${appVersion}_[contenthash].js`
return config;
}
}

This will make it easier to determine which version the user is currently using when a stale deployment occurs, but you don’t have access to the production web. You can do this by checking the access log.

Let’s try edit App.css then rebuild and see the differences:

[hash] Before App.css edited:
46.59 kB build/static/js/main.v0.1.0_aadf9cd4b96d5acf8be3.js
1.77 kB build/static/js/638.2ef45c58.chunk.js
513 B build/static/css/main.f855e6bc.css

[hash] After App.css edited:
46.59 kB build/static/js/main.v0.1.0_5540db46e3ee68dbcf2d.js
1.77 kB build/static/js/638.2ef45c58.chunk.js
521 B (+8 B) build/static/css/main.81d68a3e.css

[contenthash] Before App.css edited:
46.59 kB build/static/js/main.v0.1.0_90038a5c58153957cd6a.js
1.77 kB build/static/js/638.2ef45c58.chunk.js
513 B build/static/css/main.f855e6bc.css

[contenthash] After App.css edited:
46.59 kB build/static/js/main.v0.1.0_90038a5c58153957cd6a.js
1.77 kB build/static/js/638.2ef45c58.chunk.js
521 B (+8 B) build/static/css/main.81d68a3e.css

You can use the [contenthash] placeholder for the individual files you want to track. This ensures that only the files with changed content will have their hash updated, helping with efficient cache busting.

2. Web Server (Nginx)

Controlling how your browser behaves with caching can be done through the dance of HTTP interactions. I’m going to walk you through two approaches — one involving HTML on the request and the other through the web server for responses.

HTML
In the realm of web development, managing cache directly through HTML is a nifty way to control how browsers store and retrieve resources. One commonly used method involves leveraging the Cache-Control and Pragma directives within the <meta> tags of an HTML document.

By strategically placing these directives, developers can instruct browsers on how to handle caching, ensuring a smoother and more efficient user experience.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Cache Management Directives -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
</head>
<body>
<!-- Your web content goes here -->
</body>
</html>

Cache-Control directive signals the browser not to cache the page (no-cache), prevent storing a cached copy for future use (no-store), and to always check for updates before displaying the cached content (must-revalidate).

Additionally, the Pragma directive further enforces a no-cache policy. By incorporating these directives into your HTML, you gain more granular control over how browsers handle caching, ultimately contributing to a more dynamic and up-to-date web experience.

Nginx
Another way is by using Nginx; this is preferred method. By manipulating the cache control headers, we can force browsers to fetch the latest versions of files.

location ~* ^.+\. (css|js|png|jpg|jpeg|gif|ico)$ { 
expires 5m;
add_header Cache-Control "public, immutable";
}

This configuration set fixed expiration time for static assets. Sets a long cache expiration time and marks the resources as immutable, ensuring browsers always fetch the latest versions.

The expires directive sets an expiration time of 5 minutes for static assets (css, js, images). This means that the browser will cache these resources for 5 minutes before checking for updates.

The add_header directive appends the "Cache-Control" header to the HTTP response, specifying that the resource is public and immutable, reinforcing the cache's stability.

Before controlling cache:

[31/Dec/2023:00:00:01 +0700] GET / HTTP/1.1 411
[31/Dec/2023:00:00:01 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[31/Dec/2023:00:00:02 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 143685
[31/Dec/2023:00:00:02 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[31/Dec/2023:00:00:02 +0700] GET /logo192.png HTTP/1.1 5347
[31/Dec/2023:00:00:02 +0700] GET /favicon.ico HTTP/1.1 3870
[31/Dec/2023:00:00:02 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 143685
[31/Dec/2023:00:00] Total Size: 293.37 KB Count: 7

[31/Dec/2023:00:01:11 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:01] Total Size: 0.00 KB Count: 1

[31/Dec/2023:00:02:06 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:02:06 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 0
[31/Dec/2023:00:02:06 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 0
[31/Dec/2023:00:02:06 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 0
[31/Dec/2023:00:02:06 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 0
[31/Dec/2023:00:02] Total Size: 0.00 KB Count: 5

[31/Dec/2023:00:03:02 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:03] Total Size: 0.00 KB Count: 1

[31/Dec/2023:00:04:11 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:04:11 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 0
[31/Dec/2023:00:04:11 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 0
[31/Dec/2023:00:04:11 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 0
[31/Dec/2023:00:04:11 +0700] GET /logo192.png HTTP/1.1 5347
[31/Dec/2023:00:04:11 +0700] GET /favicon.ico HTTP/1.1 3870
[31/Dec/2023:00:04:11 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 0
[31/Dec/2023:00:04] Total Size: 9.00 KB Count: 7

[31/Dec/2023:00:05:03 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:05] Total Size: 0.00 KB Count: 1

After controlling cache:

[31/Dec/2023:00:35:18 +0700] GET / HTTP/1.1 411
[31/Dec/2023:00:35:18 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 779
[31/Dec/2023:00:35:18 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 143685
[31/Dec/2023:00:35:18 +0700] GET /static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg HTTP/1.1 2632
[31/Dec/2023:00:35:19 +0700] GET /logo192.png HTTP/1.1 5347
[31/Dec/2023:00:35:19 +0700] GET /favicon.ico HTTP/1.1 3870
[31/Dec/2023:00:35:19 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 143685
[31/Dec/2023:00:35] Total Size: 293.37 KB Count: 7

[31/Dec/2023:00:36:21 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:36:42 +0700] GET /manifest.json HTTP/1.1 492
[31/Dec/2023:00:36:42 +0700] GET /favicon.ico HTTP/1.1 3870
[31/Dec/2023:00:36:42 +0700] GET /logo512.png HTTP/1.1 9664
[31/Dec/2023:00:36:42 +0700] GET /logo192.png HTTP/1.1 5347
[31/Dec/2023:00:36] Total Size: 18.92 KB Count: 5

[31/Dec/2023:00:37:12 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:37] Total Size: 0.00 KB Count: 1

[31/Dec/2023:00:38:10 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:38] Total Size: 0.00 KB Count: 1

[31/Dec/2023:00:39:03 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:39] Total Size: 0.00 KB Count: 1

[31/Dec/2023:00:40:40 +0700] GET / HTTP/1.1 0
[31/Dec/2023:00:40:40 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 0
[31/Dec/2023:00:40:40 +0700] GET /static/css/main.f855e6bc.css HTTP/1.1 0
[31/Dec/2023:00:40:40 +0700] GET /static/js/main.v0.1.0_90038a5c58153957cd6a.js HTTP/1.1 0
[31/Dec/2023:00:40] Total Size: 0.00 KB Count: 4

3. Web Worker

Web workers provide a way to run JavaScript code in the background, separate from the main thread. We can utilize a web worker to manage caching and enforce cache busting.

Create a Cache Busting Web Worker
In your project, create a file named service-worker.js:

// service-worker.js 
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache').then((cache) => {
return cache.addAll([
// List of files to cache
'/index.html',
'/main.js',
'/styles.css',
// Add more files as needed
]);
})
);
});

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

Register the Web Worker in Your Application
In your main application file (e.g., index.js), register the web worker:

// index.js 
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch((error) => {
console.error('Service Worker registration failed:', error);
});
}

This code registers the web worker and sets up basic caching for specified files.

These three approaches provide a comprehensive cache busting strategy, ensuring that your React application stays up-to-date even after deployments. Feel free to customize each method based on your specific project requirements.

--

--