Browsing your web app is cool and seamless, until the network connection drops. Suddenly, your users are staring at that sad, gray "You are offline" dinosaur, and just like that, the experience is over. It doesn't have to be that way. Your app should load instantly, feel as reliable as a native app, and work even when the network fails.
Yep. Your web app can work without the internet. That's what Progressive Web Apps (PWAs) and an "offline-first" development gives you.
In this article, you'll learn exactly how to turn any standard web application into a robust, installable PWA. We'll dive into service workers to cache your app's core files, making your UI invincible to network issues. The principles here are universal, so you can apply them to any frontend project you're working on.
Ready? Let's go🚀
What Do You Need?
A single-page web application. It can be as simple as an index.html
file with some CSS and JavaScript. The key is that it has a few distinct files we can cache, so the main interface (the "app shell") can load even when offline.
Step 1: Creating The Web App Manifest
We want to tell the browser about our app and how it should behave when "installed." We do this with a manifest.json
file.
- Create the Manifest: In the root of your project, create a new file named
manifest.json
. - Add the Configuration: Paste the following JSON into the file. Be sure to customize the
name
,short_name
,description
andicons
for your own project.
{
"name": "My Awesome App",
"short_name": "AwesomeApp",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#007bff",
"description": "A description of my awesome PWA.",
"icons": [
{
"src": "images/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Remember you'll need to save your own icon files in an images
folder for this to work perfectly.
- Link the Manifest: Open your
index.html
and add this line inside the tag:
<link rel="manifest" href="manifest.json">
With this, browsers like Chrome will now show an "Install" icon in the address bar, allowing users to add your app to their home screen.
Step 2: Registering a Service Worker
A Service Worker is a special type of JavaScript file that your browser runs in the background, separate from your web page. It acts like a proxy, allowing network requests to be intercepted and handled in code. This is the key to offline capability.
- In your project root, create a
sw.js
file. Leave it empty for now. - Register the service worker by telling your main application to install it. Add the following script tag to the bottom of your
<body>
inindex.html
.
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('service worker registered successfully:', registration);
})
.catch(error => {
console.log('service worker registration failed:', error);
});
});
}
</script>
This code checks if the browser supports service workers and, if so, registers the sw.js
file when the page loads.
Step 3: Caching the App Shell for Offline Use
Now, let's give service worker a job to do. When the service worker is first installed, we'll tell it to download and save all the essential files that make up our app's user interface—the "app shell."
Open sw.js
and add the following code:
const CACHE_NAME = 'app-shell-cache-v1';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/styles.css', // <-- your main CSS file
'/app.js', // <-- your main JS file/entry point
'/some-other-chunk.js', // other JS files your app needs
'/favicon.ico',
'/images/icon-192.png' // <-- remember your own icon file in your images folder
];
// the install event fires when the service worker is first installed
self.addEventListener('install', event => {
console.log('service worker: installing...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('service worker: caching app shell');
return cache.addAll(ASSETS_TO_CACHE);
})
);
});
Here, we define a list of files that are critical for our UI. The install
event opens a cache with a specific name and saves those files for later. Modern JavaScript tools (like Vite or Create React App) often create multiple JS and CSS files. You must cache all the essential ones for your app to work offline. Use the Network tab in your browser's DevTools to see all the files your page requests on load and add them to this list. It's a bit tedious, but it will make your app work perfectly offline.
Step 4: Intercepting Requests and Serving from Cache
With our files cached, the final step is to tell the service worker to actually use the cache. We'll listen for any network request (fetch
event) made by our page. If the requested file is present in cache, it'll be served directly from there, completely bypassing the network.
Add this code to the bottom of your sw.js
file:
// sw.js (continued)
// the activate event fires after the install event, good place to clean up old caches
self.addEventListener('activate', event => {
console.log('service worker: activating...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== CACHE_NAME) {
console.log('service worker: clearing old cache');
return caches.delete(cache);
}
})
);
})
);
});
// the fetch event fires for every network request
self.addEventListener('fetch', event => {
console.log('service worker: fetching');
event.respondWith(
caches.match(event.request)
.then(response => {
// if the request is in the cache, return it, else fetch from the network
return response || fetch(event.request);
})
);
});
This "cache-first" strategy is perfect for the app shell. Now, even if the user is offline, as long as they have visited the site once, the service worker will intercept the request for index.html
and serve it directly from the cache.
STEP 5: Testing Your Offline-First App
The best place to test this is in Chrome DevTools.
- Open DevTools (
Ctrl+Shift+I
orCmd+Opt+I
). - Go to the Application tab.
- Under the Service Workers section, you should see your
sw.js
file is "activated and is running." You can first unregister any old service workers. - Under Cache Storage, you can inspect your
app-shell-cache-v1
and see all the files you saved. Feel free to delete your old cache.
- Check the "Offline" box at the top of the DevTools window and try refreshing your page. It should still load perfectly.
Voila😁! You've just levelled up your web app in a massive way. It's now faster, more reliable, and provides a native-like experience by being installable and working offline. These are techniques at the core of modern, high-quality web development.
And this is just the beginning of your PWA journey. From here, you could explore caching dynamic API data with IndexedDB or engaging users with push notifications.
Happy hacking!
Top comments (1)
This is very insightful. I like that you took me on a journey. Indeed, offline first web apps are so important. Thank you for this.