`Service Worker` in JavaScript
This article explains the concept of Service Worker
in JavaScript.
We will explain step-by-step from the basics of Service Worker
to practical cache control.
YouTube Video
offline.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Offline</title>
6</head>
7<body>
8 <h1>You are offline</h1>
9 <p>This is the offline fallback page.</p>
10</body>
11</html>
style.css
1body {
2 font-family: sans-serif;
3 background-color: #f0f0f0;
4 padding: 20px;
5}
6h1 {
7 color: #333;
8}
javascript-service-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <div class="container">
111 <h2>HTML Sample</h2>
112 <button id="fetchBtn">Fetch Test</button>
113 </div>
114
115 <script>
116 // Override console.log to display messages in the #output element
117 (function () {
118 // Override console.log
119 const originalLog = console.log;
120 console.log = function (...args) {
121 originalLog.apply(console, args);
122 const message = document.createElement('div');
123 message.textContent = args.map(String).join(' ');
124 output.appendChild(message);
125 };
126
127 // Override console.error
128 const originalError = console.error;
129 console.error = function (...args) {
130 originalError.apply(console, args);
131 const message = document.createElement('div');
132 message.textContent = args.map(String).join(' ');
133 message.style.color = 'red'; // Color error messages red
134 output.appendChild(message);
135 };
136 })();
137
138 document.getElementById('executeBtn').addEventListener('click', () => {
139 // Prevent multiple loads
140 if (document.getElementById('externalScript')) return;
141
142 const script = document.createElement('script');
143 script.src = 'javascript-service-worker.js';
144 script.id = 'externalScript';
145 //script.onload = () => console.log('javascript-service-worker.js loaded and executed.');
146 //script.onerror = () => console.log('Failed to load javascript-service-worker.js.');
147 document.body.appendChild(script);
148 });
149 </script>
150</body>
151</html>
Service Worker
in JavaScript
Service Worker
is a JavaScript feature that stands between the browser and the network, enabling request caching and offline support. It is a core technology of PWAs (Progressive Web Apps) and brings a native app-like experience to web applications.
What is a Service Worker
?
Service Worker
is a JavaScript file that runs in the background thread of the browser. It runs on a separate thread from the page, cannot access the UI, but can intercept network requests, manage caching, and handle push notifications.
The key features of a Service Worker
include the following:.
- It only works over HTTPS, except on localhost.
- It uses a Promise-based asynchronous API.
- It is event-driven, using events like
install
,activate
,fetch
, andpush
.
Registering a Service Worker
First, let’s write the code to register a Service Worker
in the browser.
1if ('serviceWorker' in navigator) {
2 window.addEventListener('load', () => {
3 navigator.serviceWorker.register('/sw.js')
4 .then(registration => {
5 console.log(
6 'Service Worker registered with scope:',
7 registration.scope
8 );
9 })
10 .catch(error => {
11 console.error('Service Worker registration failed:', error);
12 });
13 });
14}
Explanation
- Use
navigator.serviceWorker.register()
to register/sw.js
(theService Worker
file). - You can use
then
for success handling andcatch
for error handling during registration. registration.scope
represents the path range (scope) affected by theService Worker
.- By default, the scope is the directory where the registered file (in this case,
/sw.js
) is located and its subdirectories.
Service Worker
Scope
If you want to limit the scope, you can specify the scope
using the second argument of register
.
1navigator.serviceWorker.register('/sw.js', { scope: '/app/' })
2.then(registration => {
3 console.log(
4 'Service Worker registered with scope:',
5 registration.scope
6 );
7});
Explanation
- With this setting, only pages under
/app/
will be controlled by theService Worker
.
Creating the Service Worker
file
Next, create a file named sw.js
and implement the basic events.
1// sw.js
2const CACHE_NAME = 'my-cache-v1';
3const urlsToCache = [
4 '/',
5 '/index.html',
6 '/styles.css',
7 '/script.js',
8 '/offline.html'
9];
This code defines a list of resources to be cached.
Roles and mechanisms of each event
install
1// Install event (initial caching)
2self.addEventListener('install', event => {
3 console.log('[ServiceWorker] Install');
4 event.waitUntil(
5 caches.open(CACHE_NAME).then(cache => {
6 console.log('[ServiceWorker] Caching app shell');
7 return cache.addAll(urlsToCache);
8 })
9 );
10});
self.addEventListener('install')
is triggered when theService Worker
is registered for the first time. At this stage, necessary files are pre-cached.
activate
1// Activation event (delete old caches)
2self.addEventListener('activate', event => {
3 console.log('[ServiceWorker] Activate');
4 event.waitUntil(
5 caches.keys().then(keyList => {
6 return Promise.all(keyList.map(key => {
7 if (key !== CACHE_NAME) {
8 console.log('[ServiceWorker] Removing old cache:', key);
9 return caches.delete(key);
10 }
11 }));
12 })
13 );
14 return self.clients.claim();
15});
- In the
activate
event, old caches are deleted to optimize storage. Only the cache of the new version is kept.
fetch
1// Fetch event (cache-first strategy)
2self.addEventListener('fetch', event => {
3 console.log('[ServiceWorker] Fetch', event.request.url);
4 event.respondWith(
5 caches.match(event.request).then(response => {
6 return response || fetch(event.request).catch(() => caches.match('/offline.html'));
7 })
8 );
9});
All HTTP requests are intercepted—if a cached version exists, it is returned; otherwise, it is fetched from the network. When offline, an alternative page (e.g., offline.html
) is returned.
Confirming the operation
Let's actually check how the Service Worker
works.
1document.getElementById('fetchBtn').addEventListener('click', () => {
2 fetch('/style.css')
3 .then(response => response.text())
4 .then(data => {
5 console.log('Fetched data:', data);
6 })
7 .catch(error => {
8 console.error('Fetch failed:', error);
9 });
10});
- Here, we check the registration of the
Service Worker
and the behavior of fetching resources by clicking the test button.
Examples of Caching Strategies
The following are common caching strategies:.
Cache First
Here is an example implementation for the Cache First strategy:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 caches.match(event.request).then(response => {
4 return response || fetch(event.request);
5 })
6 );
7});
- This code implements a cache-first strategy, where the requested resource is returned from the cache if available; if not, it is fetched from the network.
Network First
Here is an example implementation for the Network First strategy:.
1self.addEventListener('fetch', event => {
2 event.respondWith(
3 fetch(event.request)
4 .then(response => {
5 return caches.open(CACHE_NAME).then(cache => {
6 cache.put(event.request, response.clone());
7 return response;
8 });
9 })
10 .catch(() => caches.match(event.request))
11 );
12});
- This code implements a network-first strategy, where the requested resource is fetched from the network first, and if that fails, it is retrieved from the cache.
Cache only styles and JavaScript, access APIs in real-time
Here is an example implementation where styles and JavaScript are cached while APIs are accessed in real-time:.
1self.addEventListener('fetch', event => {
2 if (event.request.url.includes('/api/')) {
3 // Fetch API responses in real-time without caching
4 return;
5 }
6
7 // Use cache-first strategy for static files
8 event.respondWith(
9 caches.match(event.request).then(response => {
10 return response || fetch(event.request);
11 })
12 );
13});
- This code always accesses API requests in real-time and applies a cache-first strategy to static files such as stylesheets and JavaScript.
Update flow
The update flow of a Service Worker is as follows:.
- A new
sw.js
is detected. - The
install
event is triggered. - It waits until the previous
Service Worker
becomes idle. - The
activate
event is triggered. - It switches to the new Service Worker.
- The
controllerchange
event is fired.
Update detection
Once a Service Worker
is installed, the old one continues to be used until the next visit. To apply updates, it is common to use code that detects updates and reloads the page.
1navigator.serviceWorker.addEventListener('controllerchange', () => {
2 window.location.reload();
3});
- The
controllerchange
event is fired when the controller of the Service Worker, that is, the Service Worker controlling the current page, changes. - Pages that are already open continue to use the current Service Worker, and the newly installed Service Worker does not take effect on those pages immediately. Therefore, a technique is used where the
controllerchange
event is used to detect that a new controller has become active, then the page is reloaded to immediately apply the update.
Cautions and Best Practices
When using Service Worker
, keep the following points in mind:.
-
HTTPS Required Due to security restrictions, it does not work over
http://
except onlocalhost
. -
Hashed File Names The cache name can include the file name, URL, and version information.
-
Communication with Clients Use
postMessage
to communicate between theService Worker
and the page's JavaScript.
Summary
Service Worker
is an essential technology for offline support and performance improvements in web apps. By understanding the basic flow of installation, activation, and fetch handling, and implementing appropriate caching strategies, you can build higher-quality web applications.
You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.