Best PWA Boilerplates and Starter Kits 2026
Best PWA Boilerplates and Starter Kits in 2026
TL;DR
Progressive Web Apps have had a remarkable comeback in 2026. Apple's WebKit team finally implemented full Push Notifications on iOS 16.4+, the Web Share Target API covers the most critical "native-like" gaps, and Lighthouse PWA scores now factor into Core Web Vitals reports. The best PWA starters today are not separate "PWA frameworks" — they're Next.js, Vite, or SvelteKit apps with next-pwa or vite-plugin-pwa bolted on correctly. The biggest trap is adding a service worker after the fact and breaking your caching strategy. Start with a PWA-ready template from day one if you need offline support, installability, or push notifications.
Key Takeaways
vite-plugin-pwais the gold standard for Vite, React, Vue, and SvelteKit apps — auto-generates service workers with Workbox, handles precaching, and provides excellent TypeScript supportnext-pwa(by DucanArte) is the maintained Next.js PWA plugin after the originalnext-pwapackage was abandoned — works with App Router in Next.js 15- PWA is not a replacement for native when you need deep hardware access (Bluetooth, NFC, ARKit) — but it covers 85% of "app-like" experiences
- iOS 16.4+ finally supports Web Push — the last major gap that made iOS PWAs second-class is now closed
- App Shell + Offline Page is the minimum viable PWA — cache the shell, show a custom offline page, add install prompt
- Manifest + HTTPS + Service Worker are the three requirements for installability — all PWA starters handle these automatically
What Makes a Modern PWA in 2026
A PWA is just a web app that meets three criteria for browser installability:
- HTTPS — required for service workers
- Web App Manifest —
manifest.jsonwith name, icons, theme color, display mode - Service Worker — handles offline caching, push notifications, background sync
Modern PWAs go further:
- Offline support — app shell caches all UI assets; API calls fall back to cached data
- Push notifications — via Web Push API (now works on iOS 16.4+)
- Install prompt —
beforeinstallpromptevent for a custom "Add to Home Screen" button - Background sync — queue form submissions when offline, sync when reconnected
- Share Target — app appears in the OS share sheet
The Top PWA Starters for 2026
1. Vite PWA Template (with vite-plugin-pwa)
The most production-ready starting point for React or Vue PWAs is the official vite-plugin-pwa with its starter templates.
Setup:
# Create a Vite React app
npm create vite@latest my-pwa -- --template react-ts
cd my-pwa
# Install vite-plugin-pwa
npm install -D vite-plugin-pwa
vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.yourdomain\.com\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 },
cacheableResponse: { statuses: [0, 200] },
},
},
],
},
manifest: {
name: 'My PWA App',
short_name: 'MyApp',
description: 'A Progressive Web App',
theme_color: '#1a1a2e',
background_color: '#ffffff',
display: 'standalone',
icons: [
{ src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' },
],
},
}),
],
})
Why it wins: Workbox handles all the caching complexity. NetworkFirst for API calls means users always get fresh data when online but stale cache when offline. The autoUpdate register type silently updates the service worker without disrupting the user.
The official starter repos:
- vite-pwa/create-pwa — scaffolds a new Vite PWA project
- vite-pwa/vite-plugin-pwa/examples — React, Vue, SvelteKit, Solid examples
2. SvelteKit PWA Starter
SvelteKit has the cleanest PWA story of any meta-framework in 2026. The @vite-pwa/sveltekit plugin integrates directly with SvelteKit's build and routing.
npm create svelte@latest my-pwa
cd my-pwa
npm install -D @vite-pwa/sveltekit
svelte.config.js:
import { SvelteKitPWA } from '@vite-pwa/sveltekit'
const config = {
kit: {
adapter: adapter(),
},
vite: {
plugins: [
SvelteKitPWA({
registerType: 'autoUpdate',
manifest: { name: 'SvelteKit PWA', short_name: 'SKPWA', display: 'standalone' },
workbox: { globPatterns: ['**/*.{js,css,html,svg,png,ico,txt}'] },
}),
],
},
}
SvelteKit's file-based routing and SSR combine particularly well with the App Shell caching model — the shell is served from cache, dynamic content loads from the network.
3. Next.js PWA with @ducanh2912/next-pwa
The original next-pwa by Shadowwalker was abandoned after Next.js 13. The maintained fork @ducanh2912/next-pwa works with Next.js 14/15 App Router:
npx create-next-app@latest my-next-pwa
npm install @ducanh2912/next-pwa
next.config.js:
const withPWA = require('@ducanh2912/next-pwa').default({
dest: 'public',
cacheOnFrontEndNav: true,
aggressiveFrontEndNavCaching: true,
reloadOnOnline: true,
disable: process.env.NODE_ENV === 'development',
workboxOptions: {
disableDevLogs: true,
},
})
module.exports = withPWA({
// your Next.js config
})
Limitation with App Router: Next.js App Router uses streaming SSR and React Server Components that don't cache well in Workbox's current implementation. The App Shell pattern works best with Page Router. For App Router, use the PWA primarily for manifest + install prompt + push notifications rather than aggressive offline caching.
4. Create PWA App (Standalone)
For apps that don't need a meta-framework — pure client-side PWAs like offline tools, games, or apps that authenticate entirely client-side — create-pwa from Microsoft's PWABuilder team is excellent:
npx @pwabuilder/pwa-starter create my-app
The PWABuilder starter uses Lit + Vite and generates:
- Service worker with Workbox
- Web App Manifest
- iOS/Android icon set
- Windows taskbar integration metadata
- Shortcuts for the install menu
PWABuilder also has a web tool at pwabuilder.com that takes any URL and generates the manifest, icons, and deployment packages for the Microsoft Store, Google Play (via TWA), and Apple App Store (via PWA-to-native wrappers).
Adding Push Notifications to Any PWA
iOS 16.4+ finally supports Web Push — which means you can now send push notifications to users on both Android and iOS with a single web push implementation:
// client — request notification permission and subscribe
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY),
})
// Send subscription to your server
await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
})
}
// service-worker.js — handle incoming push events
self.addEventListener('push', (event) => {
const data = event.data?.json()
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/pwa-192x192.png',
badge: '/badge-72x72.png',
data: { url: data.url },
})
)
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
event.waitUntil(clients.openWindow(event.notification.data.url))
})
Server (web-push library):
import webPush from 'web-push'
webPush.setVapidDetails(
'mailto:your@email.com',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
)
// Send a notification to a saved subscription
await webPush.sendNotification(subscription, JSON.stringify({
title: 'New message',
body: 'Alice sent you a message',
url: '/messages/123',
}))
The App Shell Architecture
The App Shell is the minimal HTML, CSS, and JavaScript required to render the UI structure. It's cached on first load and served from cache on every subsequent visit — making the app feel instantaneous.
User visits app for the first time:
1. Browser fetches app shell (HTML + CSS + JS bundle) from network
2. Service worker installs and caches the shell
3. Content loads from network
User visits app subsequently:
1. Service worker intercepts request
2. Returns app shell from cache IMMEDIATELY (< 1ms)
3. Content loads from network (or cache if offline)
Implementation with Workbox:
// In vite.config.ts (vite-plugin-pwa)
VitePWA({
workbox: {
// App Shell: precache all static assets on install
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
// Runtime caching strategies
runtimeCaching: [
// API calls: try network first, fall back to cache
{
urlPattern: /\/api\/.*/,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', networkTimeoutSeconds: 3 },
},
// Static assets: serve from cache, refresh in background
{
urlPattern: /\.(png|jpg|webp|svg|gif)$/,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: { maxEntries: 200, maxAgeSeconds: 30 * 24 * 60 * 60 },
},
},
],
},
})
Key caching strategies:
| Strategy | Use For | Trade-off |
|---|---|---|
CacheFirst | Static assets (images, fonts) | Always fast, may serve stale |
NetworkFirst | API data | Fresh when online, slow if offline |
StaleWhileRevalidate | Non-critical content | Instant response + background refresh |
NetworkOnly | Auth, payments, mutations | Never cached — fails offline |
Custom Install Prompt: The Right UX
The browser's default install banner appears once and is easily dismissed. Build a custom install prompt that appears at the right moment:
// hooks/usePwaInstall.ts
import { useEffect, useState } from 'react'
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<void>
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
}
export function usePwaInstall() {
const [installPrompt, setInstallPrompt] = useState<BeforeInstallPromptEvent | null>(null)
const [isInstalled, setIsInstalled] = useState(false)
useEffect(() => {
// Capture the browser's install event
const handleBeforeInstall = (e: Event) => {
e.preventDefault()
setInstallPrompt(e as BeforeInstallPromptEvent)
}
// Detect if already installed (display=standalone means launched from home screen)
if (window.matchMedia('(display-mode: standalone)').matches) {
setIsInstalled(true)
}
window.addEventListener('beforeinstallprompt', handleBeforeInstall)
window.addEventListener('appinstalled', () => setIsInstalled(true))
return () => window.removeEventListener('beforeinstallprompt', handleBeforeInstall)
}, [])
const install = async () => {
if (!installPrompt) return
await installPrompt.prompt()
const { outcome } = await installPrompt.userChoice
if (outcome === 'accepted') setIsInstalled(true)
setInstallPrompt(null)
}
return { canInstall: !!installPrompt && !isInstalled, isInstalled, install }
}
// Usage — show the prompt after the user has completed a meaningful action
function AfterSignupModal() {
const { canInstall, install } = usePwaInstall()
if (!canInstall) return null
return (
<div className="install-prompt">
<p>Add this app to your home screen for the best experience</p>
<button onClick={install}>Install App</button>
</div>
)
}
Best practices for install prompt timing:
- Show after the user completes signup or logs in (they've committed to the product)
- Show after 3+ visits (proven engagement)
- Never show on first page load — it's too early
- Always provide a "Not now" option and suppress for 30+ days
PWA Checklist for Production
Before shipping your PWA:
| Requirement | Check | Notes |
|---|---|---|
| HTTPS | ✅ | Required; localhost passes for development |
| Web App Manifest | ✅ | All required fields, correct display mode |
| Icons: 192x192 + 512x512 | ✅ | Plus maskable variant for Android adaptive icons |
| Service Worker registered | ✅ | Workbox handles all caching logic |
| Offline fallback page | ✅ | Custom 404/offline page when network unavailable |
| Lighthouse PWA score ≥ 90 | ✅ | Run npx lighthouse to verify |
| iOS meta tags | ✅ | apple-mobile-web-app-capable, status bar style |
| Theme color | ✅ | Matches brand, used in browser chrome |
| Push notifications | Optional | iOS 16.4+ required for iOS |
PWA vs Native App: When Each Wins
| Factor | PWA | Native (iOS/Android) |
|---|---|---|
| Development cost | One codebase | Two codebases (or React Native) |
| App Store distribution | No store required | Discovery via App Store |
| Push notifications | ✅ iOS 16.4+ | ✅ Full support |
| Offline support | ✅ via Service Worker | ✅ Full support |
| Bluetooth/NFC access | ❌ No | ✅ Full support |
| Camera/AR | Limited | ✅ Full ARKit/ARCore |
| Performance | Good (60fps for most use cases) | Excellent for complex animations |
| Install friction | Low (browser banner) | Higher (App Store) |
PWA wins for: SaaS dashboards, content apps, tools, form-based workflows, anything where the logic is the product (not the graphics).
Native wins for: Games, AR/VR, apps needing Bluetooth or NFC, apps where 60fps complex animations matter.
Methodology
- Sources: vite-plugin-pwa documentation, web.dev PWA guides, Chrome developer blog, Apple WebKit release notes (iOS 16.4 Web Push), MDN Web Docs
- Framework versions: Vite 6.x, Next.js 15.x, SvelteKit 2.x
- Data: npm download trends from npmjs.com, March 2026
Find PWA-ready boilerplates and starter kits on StarterPick — filter by offline support and push notifications.
Related: Best Next.js Boilerplates 2026 · Best SvelteKit Boilerplates 2026 · Edge-First SaaS Boilerplates: Cloudflare Workers 2026