The most surprising thing about the Vite PWA plugin is that it often makes your web app less progressive if you don’t configure it carefully.
Let’s see it in action. Imagine you have a simple Vite project:
npm create vite@latest my-pwa-app --template react
cd my-pwa-app
npm install
npm install vite-plugin-pwa
Now, let’s add the plugin to vite.config.js:
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', 'robots.txt', 'apple-touch-icon.png'],
manifest: {
name: 'My Awesome PWA',
short_name: 'MyPWA',
description: 'The best PWA ever',
theme_color: '#ffffff',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
}
})
],
})
We also need a manifest.json in the public/ directory (or index.html can link to it) and some icons.
After running npm run build, Vite will generate a dist/ folder. The PWA plugin injects a service worker, typically sw.js or service-worker.js, and updates your index.html to register it. It also bundles the manifest information into the service worker for offline caching.
The core problem this plugin solves is enabling offline functionality and installability. It does this by generating a service worker that intercepts network requests. When a user visits your site, the service worker can serve cached assets, making the app load instantly on subsequent visits, even without a network connection. It also allows the browser to prompt the user to "Add to Home Screen," making it feel more like a native app.
The registerType option is crucial. 'autoUpdate' is the default and generally what you want for a production app. It means if a new version of the service worker is detected, it will be installed in the background and take effect on the next page load or navigation. 'prompt' would ask the user if they want to update, which can be annoying. 'inline' embeds the service worker code directly into your app’s JavaScript, which is less common.
The includeAssets option tells the plugin which static assets (like favicon.ico, robots.txt, apple-touch-icon.png) should be precached by the service worker. These are often essential for the app’s identity and basic functionality.
The manifest option directly defines your web app manifest, which provides metadata like the app’s name, icons, and theme color. This is what the browser uses when displaying the "Add to Home Screen" prompt.
The true power of the service worker lies in its caching strategy, which the Vite PWA plugin abstracts but doesn’t fully dictate by default. The default precaching strategy is usually sufficient for static assets, but for dynamic content or API calls, you’ll need to configure more advanced caching. The plugin supports various strategies like 'CacheFirst', 'NetworkFirst', 'StaleWhileRevalidate', and 'NetworkOnly', which you’d specify within the workboxOptions object in your VitePWA config. For example, to use a 'StaleWhileRevalidate' strategy for API calls, you might add:
VitePWA({
// ... other options
workboxOptions: {
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24, // 1 day
},
},
},
],
},
})
This tells the service worker to first try to serve from the 'api-cache', falling back to the network if the cache is empty or expired, and then updating the cache in the background. This is how you achieve "offline-first" for dynamic data.
The most common pitfall is forgetting to explicitly precache or define caching strategies for all critical assets, especially dynamically loaded ones or those fetched via fetch or axios. If a user navigates to a page that relies on data fetched from an API, and that API call isn’t covered by a caching strategy, the page will fail to load offline. The plugin’s default precaching is good for the build output, but anything fetched at runtime needs explicit handling.
The next concept to explore is deploying your PWA and ensuring the service worker correctly updates across different environments.