Managing application state in a PWA requires handling both online and offline states, sync status, and multiple clients.
// Centralized state management for PWAs
const PWAState = {
_state: {
online: navigator.onLine,
syncNeeded: false,
pendingActions: [],
lastSync: null,
user: null,
settings: {}
},
_listeners: [],
get(key) {
return this._state[key];
},
set(key, value) {
this._state[key] = value;
this._notify(key, value);
},
subscribe(listener) {
this._listeners.push(listener);
return () => {
this._listeners = this._listeners.filter(l => l !== listener);
};
},
_notify(key, value) {
this._listeners.forEach(listener => {
try {
listener(key, value, this._state);
} catch (err) {
console.error('State listener error:', err);
}
});
},
// Persist state to localStorage
persist() {
localStorage.setItem('pwa_state', JSON.stringify(this._state));
},
// Restore state from localStorage
restore() {
try {
const saved = localStorage.getItem('pwa_state');
if (saved) {
this._state = { ...this._state, ...JSON.parse(saved) };
}
} catch (err) {
console.error('Failed to restore state:', err);
}
}
};
// Online/Offline tracking
window.addEventListener('online', () => {
PWAState.set('online', true);
syncPendingActions();
});
window.addEventListener('offline', () => {
PWAState.set('online', false);
});
// Recommended PWA project structure
my-pwa/
src/
app/
shell.js # App shell logic
router.js # Client-side routing
state.js # State management
sw/
sw.js # Service Worker entry
caching.js # Caching strategies
sync.js # Background sync
push.js # Push notifications
pages/
home/
index.html
home.js
home.css
settings/
index.html
settings.js
settings.css
components/
header.js
footer.js
offline-banner.js
utils/
db.js # IndexedDB helpers
network.js # Network detection
features.js # Feature detection
assets/
images/
fonts/
icons/
manifest.json
index.html
// Vite config for PWA
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['fonts/*.woff2', 'images/*.{png,jpg,webp}'],
manifest: {
name: 'My PWA',
short_name: 'MyPWA',
display: 'standalone',
theme_color: '#4f46e5',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' }
]
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,jpg,webp,woff2}'],
runtimeCaching: [
{
urlPattern: /\/api\//,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 300 }
}
}
]
}
})
]
});