How to use Vue with Service Workers
Service workers enable Vue applications to function offline, cache assets efficiently, and provide progressive web app capabilities for enhanced user experience. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented service worker strategies in production Vue apps throughout my 12 years of frontend development since 2014. The most effective approach is using vite-plugin-pwa with Workbox for automated service worker generation and flexible caching strategies. This method provides zero-config PWA setup, automatic asset precaching, and customizable runtime caching without manual service worker coding.
Install vite-plugin-pwa, configure plugin in vite.config.js, register service worker in main application file.
npm install vite-plugin-pwa workbox-window -D
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
vue(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
manifest: {
name: 'Vue PWA App',
short_name: 'VuePWA',
description: 'Vue application with service worker support',
theme_color: '#ffffff',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 5
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
urlPattern: /^https:\/\/cdn\.example\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'cdn-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 30
}
}
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: {
maxEntries: 60,
maxAgeSeconds: 60 * 60 * 24 * 30
}
}
}
]
}
})
]
})
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { registerSW } from 'virtual:pwa-register'
const app = createApp(App)
const updateSW = registerSW({
onNeedRefresh() {
if (confirm('New content available. Reload?')) {
updateSW(true)
}
},
onOfflineReady() {
console.log('App ready to work offline')
},
onRegistered(registration) {
console.log('Service Worker registered:', registration)
},
onRegisterError(error) {
console.error('Service Worker registration error:', error)
}
})
app.mount('#app')
<!-- src/components/ServiceWorkerStatus.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { useRegisterSW } from 'virtual:pwa-register/vue'
const {
needRefresh,
offlineReady,
updateServiceWorker
} = useRegisterSW()
const showUpdatePrompt = ref(false)
const isOnline = ref(navigator.onLine)
const updateApp = async () => {
await updateServiceWorker(true)
showUpdatePrompt.value = false
}
const closePrompt = () => {
showUpdatePrompt.value = false
needRefresh.value = false
}
onMounted(() => {
window.addEventListener('online', () => {
isOnline.value = true
})
window.addEventListener('offline', () => {
isOnline.value = false
})
if (needRefresh.value) {
showUpdatePrompt.value = true
}
})
</script>
<template>
<div class='sw-status'>
<div v-if='!isOnline' class='offline-banner'>
You are currently offline
</div>
<div v-if='offlineReady' class='offline-ready'>
App is ready to work offline
</div>
<div v-if='showUpdatePrompt || needRefresh' class='update-prompt'>
<p>New content available. Click reload to update.</p>
<button @click='updateApp'>Reload</button>
<button @click='closePrompt'>Close</button>
</div>
</div>
</template>
<style scoped>
.offline-banner {
background: #ff9800;
color: white;
padding: 0.5rem;
text-align: center;
}
.offline-ready {
background: #4caf50;
color: white;
padding: 0.5rem;
text-align: center;
}
.update-prompt {
position: fixed;
bottom: 1rem;
right: 1rem;
background: white;
border: 1px solid #ddd;
border-radius: 0.5rem;
padding: 1rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
</style>
// src/composables/useOnlineStatus.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useOnlineStatus() {
const isOnline = ref(navigator.onLine)
const updateOnlineStatus = () => {
isOnline.value = navigator.onLine
}
onMounted(() => {
window.addEventListener('online', updateOnlineStatus)
window.addEventListener('offline', updateOnlineStatus)
})
onUnmounted(() => {
window.removeEventListener('online', updateOnlineStatus)
window.removeEventListener('offline', updateOnlineStatus)
})
return {
isOnline
}
}
<!-- src/App.vue -->
<script setup>
import { useOnlineStatus } from './composables/useOnlineStatus'
import ServiceWorkerStatus from './components/ServiceWorkerStatus.vue'
const { isOnline } = useOnlineStatus()
</script>
<template>
<div id='app'>
<ServiceWorkerStatus />
<header>
<h1>Vue PWA with Service Workers</h1>
<span :class='{ online: isOnline, offline: !isOnline }'>
{{ isOnline ? 'Online' : 'Offline' }}
</span>
</header>
<main>
<p>This app works offline thanks to service workers!</p>
</main>
</div>
</template>
<style scoped>
.online {
color: green;
}
.offline {
color: orange;
}
</style>
Here the vite-plugin-pwa automatically generates service worker with Workbox integration. The registerType: ‘autoUpdate’ enables automatic updates when new service worker is available. The workbox.runtimeCaching configures different caching strategies for various resource types. NetworkFirst strategy tries network first, falling back to cache for API requests. CacheFirst strategy serves from cache first for static assets and CDN resources. The virtual:pwa-register module provides service worker registration hooks. The useRegisterSW composable exposes reactive refs for service worker state. The needRefresh ref indicates when new content is available. The updateServiceWorker function triggers service worker update and page reload.
Best Practice Note:
This is the service worker setup we use in CoreUI Vue templates for building robust progressive web applications. Configure appropriate cache expiration times based on content update frequency, implement cache versioning to prevent serving stale content after major updates, test offline functionality thoroughly including edge cases like partial cache states, and monitor service worker lifecycle events to handle updates gracefully without disrupting user experience.



