Progressive Web Apps: Building Offline-First Apps with Superpowers
Unlock your web app's true potential. Learn how to transform any website into a powerful, offline-first Progressive Web App (PWA), from plain HTML to modern frameworks like React and Vite.
PWA 2.0: Building Offline-First Apps with Superpowers
What is a PWA?
For the veterans out there, PWA stands for Progressive Web Apps. But if you are a newbie just like me, you can think of it as “web apps on steroids”.
As a formal definition, A progressive web app (PWA) is an app that's built using web platform technologies, but that provides a user experience like that of a platform-specific app. — MDN Web Docs
Why are PWAs Different?
A PWA is basically a webapp, that has some added features that a normal webapp does not have, like:
- Notifications Support.
- Ability to run in backgrounds.
- Caching support even when the app is closed.
- Multi-Platform support on a single codebase.
It basically allows you to go ahead and release your webapp, as an app, without using any native languages, like Kotlin, Swift or Objective-C. It just takes the webapp you developed, using any technology, from the basic HTML, CSS and JS page or any fancy frameworks like NextJS, React JS or Vue JS.
Now if this hooked you up, and you want to try to make your webapp a PWA, go ahead and open up your code editors.
This article covers how to make PWAs in the following technologies:
- Plain HTML, CSS and JS
- Vite + React
Requirements to Convert Your Web App to PWA.
PWAs are really simple to develop, with a minimal setup. You only need two things
- A Web App Manifest
- A Service Worker
Let’s go ahead and dive deep into what are they and why they are used.
Why is a Web Manifest Used?
The “Web Manifest” is basically a configuration file that tells the browser, the name of your app, the logo of your app, the splash screens and so on, when someone downloads your PWA.
It is a JSON document, defined by the Web Application Manifest format.
Some common used keys of the Web Manifest are:
- background_color — Defines the background color of the splash screen.
- categories — Helps classify the app into categories.
- description — Provides a short explanation of the app.
- display — Controls how the app is displayed (fullscreen, standalone, etc.).
- display_override — Specifies alternative display modes.
- file_handlers — Allows the app to open specific file types.
- icons — Defines icons for the app in different sizes.
- id — Unique identifier for the app.
- launch_handler — Controls how the app reacts when launched.
- name — Full name of the app.
- note_taking — Enables integration with note-taking features.
- orientation — Defines the default screen orientation.
- prefer_related_applications — Suggests installing related native apps.
- protocol_handlers — Lets the app handle specific URL schemes.
- related_applications — Lists apps related to this one.
- scope — Defines the navigation scope of the app.
- scope_extensions — Expands or restricts app scope.
- screenshots — Shows app previews in install prompts.
- serviceworker — Registers a service worker for offline use.
- share_target — Allows the app to receive shared content.
- short_name — Shortened name for limited space.
- shortcuts — Adds quick actions to the app’s context menu.
Now among, this only 6 keys are mandatory for a PWA, while rest can be supplied as optional fields
name
orshort_name
icons
must contain a 192px and 512px iconstart_url
display
and/ordisplay_override
prefer_related_applications
must befalse
or not present
Why is a Service Worker Used?
A “Service Worker” is basically a background script that runs separately from your web page and gives your PWA powerful capabilities like offline access, caching, and push notifications.
It is a JavaScript file, defined by the Service Worker API.
Some common used events of the Service Worker are:
- install — Triggered when the service worker is first installed.
- activate — Runs after installation to clean up old caches.
- fetch — Intercepts network requests and serves responses (from cache or network).
- message — Handles messages sent between pages and the service worker.
- push — Fired when a push notification is received.
- sync — Allows background synchronization when the device is online again.
Now among this, only registration of a service worker is mandatory for a PWA, while the rest can be supplied as optional features:
navigator.serviceWorker.register('/sw.js')
must be present to enable a service worker.
Enough talks, and buildup. Now let’s start the action.
PWA Implementation
For Plain HTML, CSS And JS
To convert your website to a PWA, is not so hard as you think. Let me walk you through.
-
Step 1: Create a Web App Manifest File
After knowing all about the Web Manifest, let us get that into action. Go ahead and create a
manifest.json
file in you root directory along withindex.html
and fill in this sample content.{ "name": "My Awesome PWA", "short_name": "AwesomePWA", "description": "My first PWA with plain HTML.", "start_url": "/index.html", "display": "standalone", "background_color": "#ffffff", "theme_color": "#000000", "icons": [ { "src": "images/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "images/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ] }
name
: The full name of your app. This appears on the splash screen and app store.short_name
: A shorter name to be used on the user's home screen, like under an icon.start_url
: The page that loads when a user opens the PWA. Using.
or/
is a common practice to load the root directory.display
: How your app should be displayed.standalone
makes it look and feel like a native app, without a browser's address bar. Other options includefullscreen
,minimal-ui
, andbrowser
.icons
: An array of objects defining your app's icons in various sizes. The browser uses these to display the app on the home screen, splash screen, and notifications. You must create these icon files and place them in the specified path (e.g.,images/
).
-
Step 2: Link the Manifest to Your HTML
Now, you need to tell your HTML page where to find the manifest file. In the
<head>
section of yourindex.html
file, add the following<link>
tag:<link rel="manifest" href="/manifest.json" />
This tag creates the connection. Once a user visits your site, the browser will fetch this file and understand that your website has PWA capabilities.
-
Step 3: Create and Register a Service Worker
The service worker is a JavaScript file that runs in the background, separate from your main web page. Its primary job is to act as a programmable network proxy, intercepting network requests and allowing you to control how your app handles caching and offline content.
First, create a new file named
sw.js
(for "service worker") in the root directory of your project. You can give it any name, we just need to make connections into ourindex.html
const CACHE_NAME = 'my-pwa-cache-v1'; const urlsToCache = [ '/', '/index.html', '/styles.css', '/scripts.js', '/images/icon-192x192.png', '/images/icon-512x512.png' ]; self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); }); self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then((response) => { if (response) { return response; } return fetch(event.request); }) ); });
install
event: This event is fired when the service worker is installed. Inside the event listener, we open a cache namedmy-pwa-cache-v1
and add all the files listed inurlsToCache
to it. This ensures that these key assets are available for offline use.fetch
event: This event is fired every time the browser tries to fetch a resource (like an image, CSS file, or another HTML page). The code first checks if the requested resource exists in the cache. If it does, it serves the cached version, making the app faster and available offline. If not, it fetches the resource from the network.
-
Step 4: Register the Service Worker
Now, you need to tell your website to use this service worker. In a separate JavaScript file (e.g.,
scripts.js
or directly in a<script>
tag in yourindex.html
), add the following code:if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then((registration) => { console.log('Service Worker registered! Scope:', registration.scope); }) .catch((err) => { console.log('Service Worker registration failed:', err); }); }); }
This script checks if the browser supports service workers. If it does, it registers
sw.js
. The registration process defines the scope, which determines which files the service worker can control. By placingsw.js
in the root, its scope is the entire website. -
Step 5: Host Your Site Over HTTPS
This is a critical requirement. A PWA must be served over a secure connection (HTTPS). This is because service workers have significant power (like intercepting requests), and browsers require this security to protect users from malicious attacks. During development, you can use
localhost
, which is considered a secure context. -
Step 6: Test Your PWA
To test your PWA, you can use the Lighthouse tool, which is built into Google Chrome's DevTools. Open your website, go to DevTools (F12), select the "Lighthouse" tab, and run an audit for "Progressive Web App." Lighthouse will provide a report, telling you what you've done right and what you need to fix to meet PWA standards.
For Vite + React App
Let’s say you already have developed a react app using Vite as your build tool. For a Vite + React app, the process of developing a PWA is significantly different from a plain HTML site because you can leverage a build tool plugin to handle most of the heavy lifting for you. Instead of manually creating and managing the manifest and service worker files, you'll use a package that automates this process. The most popular choice for Vite is vite-plugin-pwa
.
Here is a detailed guide on how to add PWA functionality to your existing Vite + React application.
-
Step 1: Install the Vite PWA Plugin
The first step is to add
vite-plugin-pwa
to your project as a development dependency. This is a crucial package that will handle the generation of the manifest file, the service worker, and the logic to register them.Open your terminal in the project's root directory and run the following command
npm install vite-plugin-pwa -D
If you use a different package manager, the command will be slightly different (e.g.,
yarn add vite-plugin-pwa -D
). -
Step 2: Configure the Plugin in
vite.config.js
This is where the magic happens. You'll import the plugin and add it to the
plugins
array in yourvite.config.js
(orvite.config.ts
) file. You can then configure the PWA's properties directly in this file, which is much more convenient than a separate manifest file.Open your
vite.config.js
and update it as follows:import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { VitePWA } from 'vite-plugin-pwa'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ react(), VitePWA({ registerType: 'autoUpdate', includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'], manifest: { name: 'My React PWA', short_name: 'ReactPWA', description: 'My awesome React Progressive Web App!', theme_color: '#ffffff', icons: [ { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' }, { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' } ] } }) ] });
Here's a breakdown of the key configuration options:
registerType: 'autoUpdate'
: This tells the plugin to automatically register the service worker and update it whenever a new version of your app is deployed.includeAssets
: An array of assets that should be pre-cached. This is often used for favicons and other static files in your public folder.manifest
: This object is where you define the contents of your web app manifest. The plugin will automatically generate themanifest.json
file based on these properties. This includes the name, short name, colors, and icons for your app. 🎨
-
Step 3: Add the PWA Icons
Even with the plugin, you still need to provide the image files for your app's icons. The manifest configuration in the previous step references these files, so you must create them and place them in your
public
folder.Create two icons in a graphics editor:
pwa-192x192.png
(a 192x192 pixel icon)pwa-512x512.png
(a 512x512 pixel icon)
Place them in the
public
folder of your project. Thevite-plugin-pwa
plugin will automatically include and manage these assets. -
Step 4: Add the PWA to
index.html
Unlike a plain HTML site where you manually link the manifest,
vite-plugin-pwa
handles this for you. However, you should ensure yourindex.html
file has a<head>
section with a proper<title>
and a<meta name="theme-color">
tag to match your manifest.<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="theme-color" content="#ffffff" /> <title>My React PWA</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.jsx"></script> </body> </html>
The plugin will dynamically inject the necessary manifest link and service worker registration script during the build process.
-
Step 5: Test and Deploy
Since Vite and
vite-plugin-pwa
are designed for modern web development, testing and deployment are straightforward.- Development: To test locally, simply run
npm run dev
. The plugin will use a mock service worker for development so you can debug offline functionality easily. - Production: To get the full PWA experience, you need to build your application and serve it.
- Run
npm run build
to create a production-ready build in thedist
folder. - Serve the
dist
folder using a static server. You can install a simple one for testing withnpm install -g serve
and then runserve -s dist
. This will serve your PWA onlocalhost
, which is a secure context.
- Run
- Development: To test locally, simply run
Once you have your production build served over HTTPS, you can open it in a modern browser like Chrome. Look for the "install" button in the address bar to see if your PWA is ready to be installed! You can also use the Lighthouse tool in Chrome DevTools to audit your PWA and check if it meets all the criteria.
This approach is much more efficient than the manual process because the plugin handles file generation, caching strategies, and asset revisioning, ensuring your PWA is always up-to-date and reliable.
Final Words
That's it from this already long article. If this article get's enough views and helps you out, I might release another article soon for Next.JS and React + CRA cli.
I would love to hear from you. Let's connect on Instagram