Introduction
PWA Template 2025 is a production-ready Progressive Web App template built with modern vanilla JavaScript, offering enterprise-grade features without framework overhead.
What is a Progressive Web App?
A Progressive Web App (PWA) is a web application that uses modern web capabilities to deliver an app-like experience to users. PWAs combine the best of web and native apps:
- Installable - Users can add your app to their home screen without app stores
- Offline-First - Works seamlessly even without internet connectivity
- Responsive - Fits any device form factor: desktop, mobile, tablet
- Fresh - Always up-to-date thanks to service worker updates
- Safe - Served via HTTPS to prevent tampering
- Discoverable - Identifiable as applications via manifest files
- Re-engageable - Push notifications and background sync capabilities
Technology Stack
This template leverages modern web standards and best practices:
Frontend
- Vanilla JavaScript (ES6+) - Clean, modular class-based architecture
- HTML5 - Semantic markup with ARIA attributes
- CSS3 - Modern styling with CSS Grid, Flexbox, and Custom Properties
- Service Workers - Cache-first strategies for offline functionality
- Web APIs - IndexedDB, LocalStorage, Intersection Observer, Fetch API
Build System
- Webpack 5 - Module bundling with code splitting and tree shaking
- Babel - ES6+ transpilation for broad browser support
- PostCSS - CSS processing with Autoprefixer
- Terser - JavaScript minification and optimization
- Workbox - Service worker generation and management
Performance Features
- Code Splitting - Separate vendor and application bundles
- Resource Hints - Preconnect, prefetch, and speculation rules
- Lazy Loading - Images and media load on-demand
- Core Web Vitals Optimization - LCP, FID, CLS tracking and optimization
- Compression - Gzip and Brotli support for smaller payloads
Architecture Overview
The application follows a modular architecture with clear separation of concerns:
- State Management - Reactive state with observer pattern
- Navigation Controller - Client-side routing with view transitions
- API Adapter - RESTful API communication layer
- Offline Queue - Request queuing for background sync
- Storage Manager - Unified interface for LocalStorage and IndexedDB
- Performance Monitoring - Real User Monitoring (RUM) and Core Web Vitals
- Accessibility Manager - WCAG 2.1 AA compliance utilities
- Media Handler - Responsive images and lazy loading
Installation
Prerequisites
Before you begin, ensure you have the following installed:
- Node.js 18.x or higher
- npm 9.x or higher
Quick Start
# Clone the repository
git clone https://github.com/your-username/pwa-template-2025.git
# Navigate to project directory
cd pwa-template-2025
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run buildProject Structure
pwa-template-2025/
├── src/
│ ├── js/
│ │ ├── app.js # Main application entry
│ │ ├── state-manager.js # State management
│ │ ├── navigation.js # Navigation controller
│ │ ├── security.js # Security utilities
│ │ └── ... # Other modules
│ ├── css/
│ │ ├── main.css # Main styles
│ │ ├── design-tokens.css # Design system tokens
│ │ └── view-transitions.css # Transition styles
│ ├── index.html # Main HTML template
│ ├── sw.js # Service worker
│ └── ... # Other HTML pages
├── public/
│ ├── manifest.json # PWA manifest
│ └── icons/ # App icons
├── webpack.config.js # Webpack configuration
└── package.json # DependenciesConfiguration
Manifest Configuration
Update public/manifest.json with your app details:
{
"name": "Your App Name",
"short_name": "YourApp",
"description": "Your app description",
"theme_color": "#3b82f6",
"background_color": "#ffffff"
}Design Tokens
Customize your app's appearance in src/css/design-tokens.css:
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--spacing-unit: 4px;
--font-family-base: system-ui, sans-serif;
}Service Worker
The service worker is the heart of PWA functionality, providing offline capabilities, background sync, and intelligent caching. It acts as a network proxy between your app and the internet.
What is a Service Worker?
A service worker is a JavaScript file that runs in the background, separate from the main browser thread. Key characteristics:
- Runs independently of the application page
- Cannot directly access the DOM
- Operates as a programmable network proxy
- Requires HTTPS (except on localhost for development)
- Enables offline-first functionality
Caching Strategies
Our template implements multiple caching strategies optimized for different resource types:
Cache First (Cache Falling Back to Network)
Best for static assets that rarely change:
- Use for: Images, fonts, CSS files, JavaScript bundles
- Benefit: Fastest loading, works fully offline
- How it works: Checks cache first, only fetches from network if not cached
Network First (Network Falling Back to Cache)
Best for frequently updated content:
- Use for: API calls, dynamic data, HTML pages
- Benefit: Always fresh when online, graceful offline fallback
- How it works: Tries network first, uses cache if offline
Stale While Revalidate
Best for content that can be slightly outdated:
- Use for: Social media feeds, news articles, user avatars
- Benefit: Instant loading with background updates
- How it works: Returns cached version immediately, updates cache in background
Service Worker Lifecycle
// 1. Registration (in app.js)
navigator.serviceWorker.register('/sw.js')
// 2. Installation (in sw.js)
self.addEventListener('install', (event) => {
// Cache critical assets
event.waitUntil(
caches.open(CACHE_NAME).then(cache =>
cache.addAll(urlsToCache)
)
);
});
// 3. Activation (in sw.js)
self.addEventListener('activate', (event) => {
// Clean up old caches
event.waitUntil(
caches.keys().then(cacheNames =>
Promise.all(
cacheNames
.filter(name => name !== CACHE_NAME)
.map(name => caches.delete(name))
)
)
);
});
// 4. Fetch Interception (in sw.js)
self.addEventListener('fetch', (event) => {
// Apply caching strategy
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});Customizing Cache Behavior
// Example: Add custom routes to cache
const CACHE_NAME = 'my-app-v2';
const urlsToCache = [
'/',
'/index.html',
'/about',
'/features',
'/css/main.css',
'/js/app.bundle.js'
];
// Cache images separately with size limit
const IMAGE_CACHE = 'images-v1';
const MAX_IMAGE_CACHE_SIZE = 50; // 50 images maxTesting Service Worker
Debug your service worker using browser DevTools:
- Chrome: DevTools → Application → Service Workers
- Firefox: DevTools → Application → Service Workers
- Check "Update on reload" during development
- Use "Unregister" to test fresh installations
- Monitor "Cache Storage" to verify cached resources
Offline Support
The template includes comprehensive offline support with an offline queue for deferred operations.
Using the Offline Queue
// Queue an operation for when online
const queue = window.app.getModule('offlineQueue');
await queue.add({
url: '/api/save',
method: 'POST',
data: { name: 'example' }
});State Management
Simple reactive state management with observer pattern.
Basic Usage
// Get state
const state = window.app.getState();
// Update state
state.user.name = 'John Doe';
// Subscribe to changes
window.app.state.subscribe((newState, oldState) => {
console.log('State changed:', newState);
});Performance Optimization
This PWA template is meticulously optimized for Google's Core Web Vitals and real-world performance metrics.
Core Web Vitals
The three key metrics that Google uses to measure user experience:
Largest Contentful Paint (LCP)
- Target: <2.5 seconds
- Measures: Loading performance
- Our optimizations:
- Critical CSS inlined in HTML head
- Image lazy loading with native loading="lazy"
- Preconnect to external domains
- Efficient cache-first service worker strategy
First Input Delay (FID)
- Target: <100 milliseconds
- Measures: Interactivity
- Our optimizations:
- Minimal JavaScript execution on main thread
- Code splitting to reduce bundle size
- Service Worker runs in background thread
- Event delegation for better performance
Cumulative Layout Shift (CLS)
- Target: <0.1
- Measures: Visual stability
- Our optimizations:
- Explicit width/height attributes on images
- Proper font loading strategy with font-display
- Reserved space for dynamic content
- No ads or dynamically injected content above fold
Code Splitting & Bundle Optimization
// Webpack automatically splits vendor code
// app.bundle.js - Your application code (~50KB)
// vendor.bundle.js - Third-party libraries (~150KB)
// Dynamic imports for route-based splitting
async function loadFeature() {
const { FeatureModule } = await import('./feature.js');
return new FeatureModule();
}
// This creates a separate chunk loaded on-demandResource Hints & Preloading
<!-- Preconnect to API domain -->
<link rel="preconnect" href="https://api.example.com">
<!-- Prefetch next likely page -->
<link rel="prefetch" href="/about">
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/main.woff2" as="font"
type="font/woff2" crossorigin>
<!-- DNS prefetch for third-party resources -->
<link rel="dns-prefetch" href="https://analytics.example.com">Image Optimization
<!-- Native lazy loading -->
<img src="image.jpg" loading="lazy"
width="800" height="600" alt="Description">
<!-- Responsive images with srcset -->
<img srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px"
src="medium.jpg" alt="Description">
<!-- Modern formats with fallback -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="Description">
</picture>Speculation Rules API
Intelligent prefetching of likely next pages:
// Automatically prefetch links user hovers over
// Configured in speculation-rules.js
{
"prerender": [
{"source": "list", "urls": ["/about", "/features"]}
],
"prefetch": [
{"source": "document", "where": {"href_matches": "/*"}}
]
}Real User Monitoring (RUM)
Track actual user performance metrics:
// RUM collector tracks:
// - Core Web Vitals (LCP, FID, CLS)
// - Navigation timing
// - Resource loading times
// - User interactions
// - Network conditions
// Access performance data
const rum = window.app.getModule('rum');
const metrics = rum.getMetrics();Performance Budget
Recommended targets for this template:
- Initial Load: <3s on 3G connection
- JavaScript Bundle: <200KB total (gzipped)
- CSS: <50KB (gzipped)
- Images per page: <1MB total
- Lighthouse Score: >90 in all categories
Security
Security is built into every layer of this PWA template, following modern web security best practices.
HTTPS Requirement
PWAs require HTTPS for production deployment:
- Why: Service Workers have powerful capabilities that must be secure
- Exception: localhost is allowed for development
- Free options: Let's Encrypt, Cloudflare, Netlify, Vercel
Content Security Policy (CSP)
CSP prevents cross-site scripting (XSS) attacks by controlling resource loading:
<!-- Recommended CSP configuration -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
worker-src 'self';
manifest-src 'self';
">Input Sanitization
The SecurityManager class provides robust input sanitization:
import { SecurityManager } from './js/security.js';
const security = new SecurityManager();
// Sanitize HTML to prevent XSS
const userInput = '<script>alert("xss")</script>';
const safe = security.sanitize(userInput);
// Result: <script>alert("xss")</script>
// Validate and sanitize URLs
const url = security.sanitizeURL(userProvidedURL);
// Escape special characters for safe display
const escaped = security.escapeHTML(userContent);Secure Data Storage
Best practices for storing user data:
- Never store sensitive data: Passwords, credit cards, tokens in localStorage
- Use IndexedDB: For larger datasets with better security
- Encrypt sensitive data: Before storing client-side
- Session tokens: Use httpOnly cookies when possible
// Good: Use StorageManager for non-sensitive data
const storage = window.app.getModule('storage');
await storage.set('preferences', { theme: 'dark' });
// Bad: Don't store sensitive data
// localStorage.setItem('password', 'secret123'); ❌API Communication
Secure API interactions with the APIAdapter:
const api = window.app.getAPI();
// Automatically includes CSRF tokens
// Validates response content types
// Sanitizes responses before processing
const data = await api.get('/user/profile');
// Use proper authentication headers
api.setHeader('Authorization', `Bearer ${token}`);Privacy Considerations
- Consent Management: GDPR/CCPA compliant consent system
- Privacy-First Analytics: No tracking without user consent
- No Third-Party Tracking: No external analytics by default
- Data Minimization: Only collect necessary information
- User Control: Easy data export and deletion
Service Worker Security
Service Worker best practices:
- Serve sw.js from root domain (never CDN)
- Version your caches to prevent stale data
- Validate all cached responses
- Clear old caches during activation
- Use network-first for sensitive API calls
Customization
Changing Colors
Update design tokens in src/css/design-tokens.css
Adding New Pages
- Create new HTML file in
src/ - Add entry to webpack config if needed
- Update navigation in header
- Add route to service worker cache
Custom Modules
// Create new module
class MyModule {
constructor() {
this.init();
}
init() {
console.log('Module initialized');
}
}
// Register in app.js
this.modules.myModule = new MyModule();Deployment
Build for Production
npm run buildDeployment Checklist
- ✅ Update manifest.json with production URLs
- ✅ Configure HTTPS
- ✅ Set up proper caching headers
- ✅ Enable compression (Gzip/Brotli)
- ✅ Test on real devices
- ✅ Run Lighthouse audit
Hosting Platforms
The template works with any static hosting platform:
- Netlify
- Vercel
- GitHub Pages
- Cloudflare Pages
API Reference
PWAApp Class
The main application class that orchestrates all modules:
// Access global app instance
window.app
// Core Methods
app.getState() // Get current application state
app.getAPI() // Get API adapter instance
app.getModule(name) // Get registered module by name
app.toggleTheme() // Toggle between dark/light mode
app.promptInstall() // Show PWA install promptStateManager
Reactive state management with observer pattern:
const state = window.app.getState();
// Read state
console.log(state.user.name);
console.log(state.app.theme);
// Update state (triggers observers)
state.user = { name: 'John', role: 'admin' };
state.app.isOnline = false;
// Subscribe to state changes
window.app.state.subscribe((newState, oldState) => {
console.log('State changed:', newState);
});
// Get entire state object
const fullState = window.app.state.getState();APIAdapter
HTTP client for RESTful API communication:
const api = window.app.getAPI();
// GET request
const user = await api.get('/users/123');
// POST request
const newUser = await api.post('/users', {
name: 'Jane',
email: '[email protected]'
});
// PUT request
const updated = await api.put('/users/123', { name: 'John Doe' });
// DELETE request
await api.delete('/users/123');
// Set custom headers
api.setHeader('Authorization', `Bearer ${token}`);StorageManager
Unified interface for browser storage:
const storage = window.app.getModule('storage');
// LocalStorage operations
await storage.set('key', { data: 'value' });
const data = await storage.get('key');
await storage.remove('key');
await storage.clear();
// IndexedDB for larger data
await storage.setIDB('largeDataset', bigArray);
const bigData = await storage.getIDB('largeDataset');OfflineQueue
Queue requests when offline, sync when online:
const queue = window.app.getModule('offlineQueue');
// Add request to queue
await queue.add({
url: '/api/save',
method: 'POST',
data: { title: 'Draft', content: '...' },
priority: 1 // Higher = more important
});
// Check queue status
const pending = queue.getPending();
console.log(`${pending.length} requests queued`);
// Queue automatically syncs when online
// Manual sync if needed
await queue.sync();AccessibilityManager
WCAG 2.1 AA compliance utilities:
const a11y = window.app.getModule('accessibility');
// Announce to screen readers
a11y.announce('Form submitted successfully');
// Focus management
a11y.focusElement(document.querySelector('#result'));
a11y.trapFocus(modalElement);
a11y.releaseFocus();
// Keyboard navigation
a11y.enableKeyboardShortcuts({
'Ctrl+K': () => openSearch(),
'Escape': () => closeModal()
});MediaHandler
Lazy loading and responsive images:
const media = window.app.getModule('media');
// Initialize lazy loading on images
media.lazyLoad('.lazy-image');
// Dynamically add responsive image
const img = media.createResponsiveImage({
src: 'image.jpg',
srcset: 'small.jpg 400w, large.jpg 800w',
alt: 'Description',
loading: 'lazy'
});PrivacyAnalytics
Privacy-first analytics with consent:
const analytics = window.app.getModule('analytics');
// Track page view (only if user consented)
analytics.track('page_view', {
url: window.location.pathname,
referrer: document.referrer
});
// Track custom event
analytics.track('button_click', {
buttonId: 'signup',
location: 'header'
});
// Get analytics status
console.log(analytics.isEnabled()); // false if no consentViewTransitionManager
Smooth page transitions:
const vt = window.app.getModule('viewTransitions');
// Transitions automatically work on links with data-transition
// <a href="/page" data-transition="fade">Link</a>
// Programmatic transition
await vt.transition(() => {
// Update DOM here
document.querySelector('#content').innerHTML = newContent;
}, 'slide');RUMCollector
Real User Monitoring metrics:
const rum = window.app.getModule('rum');
// Get all collected metrics
const metrics = rum.getMetrics();
// Core Web Vitals
console.log('LCP:', metrics.lcp); // Largest Contentful Paint
console.log('FID:', metrics.fid); // First Input Delay
console.log('CLS:', metrics.cls); // Cumulative Layout Shift
// Navigation timing
console.log('DOM Load:', metrics.domContentLoaded);
console.log('Page Load:', metrics.loadComplete);ErrorBoundary
Global error handling:
const errors = window.app.getModule('errorBoundary');
// Errors are automatically caught and logged
// Access error history
const recentErrors = errors.getErrors();
// Custom error handler
errors.onError((error, errorInfo) => {
// Send to error tracking service
console.error('App error:', error);
});FAQ
What browsers support PWAs?
Full Support: Chrome, Edge, Samsung Internet, Opera (all Chromium-based)
iOS Support: Safari 11.1+ supports PWA installation with some limitations (no push notifications, no background sync)
Desktop: Windows, macOS, Linux, ChromeOS all support installable PWAs
How do I change the app name and branding?
Update these files:
public/manifest.json- Changenameandshort_name- All HTML files - Update
<title>tags src/css/design-tokens.css- Update colors and themepublic/icons/- Replace icon files (maintain sizes)
Can I connect this to a backend API?
Yes! The APIAdapter is designed for this. Configure in src/js/app.js:
this.api = new APIAdapter({
baseURL: 'https://api.yourapp.com',
timeout: 7000,
headers: {
'Authorization': 'Bearer token'
}
});The template includes offline queue support for reliable API communication.
How do I enable push notifications?
Push notifications require:
- HTTPS domain (required for service workers)
- VAPID keys for web push
- Backend service to send notifications
- User permission (browser will prompt)
The service worker is ready for push notifications. Add your VAPID keys and notification handlers to src/sw.js.
Can I use this with React, Vue, or other frameworks?
The template uses vanilla JavaScript, but PWA features (service worker, manifest, caching) work with any framework:
- Service Worker: Framework-agnostic, works with all
- Manifest: Standard JSON file, no framework dependency
- Modules: Can be imported into React/Vue components
- Architecture: Patterns translate easily to frameworks
Or use the entire template as-is - vanilla JS is fast and has zero dependencies!
Why isn't my PWA installable?
PWA installation requires all of these:
- ✓ Served over HTTPS (or localhost)
- ✓ Valid
manifest.jsonwith name, icons, start_url - ✓ Service worker registered and active
- ✓ At least 192x192 and 512x512 icons
- ✓ User has visited site at least twice (some browsers)
Check Chrome DevTools → Application → Manifest for validation errors.
How big should my app icons be?
Required icon sizes for PWAs:
- 192x192 - Minimum required for Android
- 512x512 - Required for splash screens
- 144x144, 96x96, 72x72, 48x48 - Recommended for various devices
- 180x180 - Apple Touch Icon for iOS
Use PNG format with transparency. Tools like RealFaviconGenerator can help.
Does this work offline completely?
Yes! The service worker caches:
- All HTML pages
- CSS and JavaScript bundles
- Images and fonts
- API responses (configurable)
First visit requires internet to download assets. After that, the app works fully offline. The offline queue stores failed API requests and syncs when back online.
How do I test PWA features locally?
Testing checklist:
- DevTools: Chrome DevTools → Application tab
- Service Worker: Check registration and cache
- Offline Mode: DevTools → Network tab → Offline checkbox
- Lighthouse: Run audit for PWA compliance
- Install: Chrome shows install icon in address bar
Service workers only activate on page reload after registration.
What's the difference between localStorage and IndexedDB?
LocalStorage:
- Simple key-value storage
- 5-10MB limit
- Synchronous API (blocks main thread)
- Good for: Settings, small preferences
IndexedDB:
- Full database with indexes and queries
- 50MB+ limit (can request more)
- Asynchronous API (non-blocking)
- Good for: Large datasets, offline data, complex structures
Troubleshooting
Service Worker Not Registering
- Ensure you're using HTTPS or localhost
- Check browser console for errors
- Verify sw.js is being served correctly
- Clear browser cache and hard reload
App Not Installing
- Verify manifest.json is valid
- Ensure all required icons exist
- Check service worker is registered
- Test on a supported browser (Chrome, Edge, Safari)
Build Errors
- Delete node_modules and reinstall:
rm -rf node_modules && npm install - Clear webpack cache:
rm -rf dist - Check Node.js version compatibility