Opinionated rules for building production-grade Progressive Web Apps (PWAs) with TypeScript, React and Workbox.
Stop settling for web apps that break offline, crawl on mobile networks, and fail Lighthouse audits. This PWA Mastery Ruleset transforms your development workflow into a production-grade PWA pipeline that consistently delivers sub-1.8s load times and flawless offline experiences.
You're shipping PWAs that users abandon within seconds. Here's what's killing your conversion rates:
These aren't just technical debt—they're revenue killers. Every failed offline interaction and slow load time drives users to your competitors' native apps.
This ruleset implements battle-tested patterns from high-traffic PWAs handling millions of users. You get:
Offline-First Service Worker Pipeline: Workbox v7 configuration that automatically handles cache-first for static assets, network-first for API calls, and stale-while-revalidate for images. Your app works seamlessly whether users are on fiber or airplane mode.
Performance Budget Enforcement: Automated CI checks that fail builds when JavaScript bundles exceed 100KB or First Contentful Paint hits 1.8s on simulated 3G. No more performance regressions slipping into production.
Smart Code Splitting Strategy: Route-level splitting with intelligent prefetching that loads next pages while users read current content. First page loads instantly, subsequent navigation feels native.
Cut debugging time by 70%: Structured error boundaries with offline fallbacks mean network issues don't crash your app. Users see helpful messages, you get actionable error reports.
Eliminate cache invalidation headaches: Self-healing cache management automatically purges outdated content when you deploy. Users always see fresh data without manual cache clearing.
Reduce QA cycles by 50%: Comprehensive testing suite includes offline simulation, 3G throttling, and automated Lighthouse audits. Catch performance regressions before they reach users.
Instead of manually checking bundle sizes and running performance audits, your CI pipeline automatically:
Your PWA ships with:
# Start with optimized Vite + React setup
npm create vite@latest my-pwa -- --template react-ts
cd my-pwa && npm install
# Add PWA essentials
npm install workbox-webpack-plugin @vitejs/plugin-pwa
npm install -D @playwright/test lighthouse
// vite.config.ts - Production-grade PWA config
import { VitePWA } from '@vitejs/plugin-pwa'
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
runtimeCaching: [
// Static assets: cache-first
{
urlPattern: /\.(?:js|css|html|ico|png|svg)$/,
handler: 'CacheFirst',
options: {
cacheName: 'static-assets',
expiration: { maxEntries: 50, maxAgeSeconds: 31536000 }
}
},
// API calls: network-first with offline fallback
{
urlPattern: /^https:\/\/api\./,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
networkTimeoutSeconds: 3
}
}
]
}
})
]
})
// utils/fetch-retry.ts - Network resilience
export async function fetchWithRetry(
url: string,
options: RequestInit = {},
retries = 3
): Promise<Response> {
try {
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(5000)
})
if (!response.ok && retries > 0) {
await new Promise(resolve => setTimeout(resolve, 1000))
return fetchWithRetry(url, options, retries - 1)
}
return response
} catch (error) {
if (retries > 0) {
return fetchWithRetry(url, options, retries - 1)
}
// Return cached data if available
const cache = await caches.open('api-cache')
const cached = await cache.match(url)
if (cached) return cached
throw error
}
}
# .github/workflows/pwa-ci.yml
name: PWA Quality Pipeline
on: [push, pull_request]
jobs:
quality-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
# Performance budget enforcement
- name: Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
# Offline functionality testing
- name: Playwright Tests
run: |
npx playwright install
npm run test:e2e
Development Velocity: Ship PWAs 40% faster with pre-configured TypeScript, testing, and performance tooling. No more researching best practices or debugging service worker edge cases.
User Experience Metrics: Achieve consistent Lighthouse scores >90 across Performance, Best Practices, and SEO. Your PWAs load sub-2s on 3G networks worldwide.
Production Stability: Eliminate offline crashes and cache invalidation bugs that plague typical PWA deployments. Users get reliable experiences regardless of network conditions.
Team Confidence: Deploy PWAs knowing they work across all browsers, handle network failures gracefully, and meet accessibility standards out of the box.
Your PWA development just leveled up from amateur hour to enterprise-grade. Implement these rules today and start shipping PWAs that users actually want to install and keep using.
You are an expert in Progressive Web Apps built with TypeScript, React 18+, Service Workers (Workbox v7), Vite 5, Webpack 5, HTTP/3, and modern browser APIs.
Key Principles
- Offline-first, network-enhanced: the app must remain functional without connectivity and progressively improve when online.
- Progressive enhancement: feature-detect APIs (e.g., navigator.serviceWorker, navigator.share) and layer optional functionality.
- Mobile-first UI, responsive at every breakpoint, 60 FPS target.
- Treat performance as a feature: budget ≤ 100 KiB critical JS, FCP < 1.8 s on 3G.
- Security & privacy by default: enforce HTTPS, CSP, permission transparency, minimal tracking.
- Accessibility is non-negotiable: WCAG 2.2 AA baseline.
- Automate everything: linting, formatting, tests, Lighthouse CI, bundle-size budgets in CI.
TypeScript
- Always use strict mode and "importsNotUsedAsValues": "error".
- Prefer interfaces for public contracts and types for utility compositions.
- Use ES2022 module syntax; avoid default exports—prefer named exports.
- Function signature order: (params, options?, deps?) => Promise<Result> | Result.
- File naming: kebab-case for modules (e.g., service-worker.ts), PascalCase for React components.
- Keep files ≤ 300 lines; split UI + logic: Component.tsx, Component.logic.ts, Component.styles.ts.
- Never access DOM directly in components—use refs/helpers.
Error Handling and Validation
- Guard clauses at top of functions: return early on bad input or feature absence.
- Wrap every network request with retry + timeout (fetch-retry wrapper). Return cached data when offline.
- Use Workbox runtimeCaching fallback handlers (cache-first/static, network-first/dynamic, stale-while-revalidate/images).
- Add self-healing cache: on version change, purge old caches via serviceWorker.skipWaiting() + clients.claim().
- Surface UX-friendly errors (toast/snackbar). Log technical details to Sentry only in production.
React (Next.js / CRA / Vite-React)
- Functional components with hooks only; no class components.
- Co-locate data + UI with react-query v5 (or SWR) for cache hydration; use offlinePersistence to IndexedDB.
- Wrap <App/> with ErrorBoundary + Suspense fallback skeletons.
- Use dynamic import() for route-level code-splitting; specify webpackChunkName.
- Generate service worker via Workbox plugin (next-pwa or vite-plugin-pwa) with the following config:
- "maximumFileSizeToCacheInBytes": 5 * 1024 * 1024
- runtimeCaching rules: see Error Handling section.
- Use manifest.webmanifest with:
- name, short_name ≤ 12 chars, theme_color matching brand, display: "standalone", prefer_related_applications: false.
- Prompt installation via beforeinstallprompt; show custom toast only once per version.
Additional Sections
Testing
- Unit: Vitest/Jest, 100% coverage on utilities and hooks.
- E2E: Playwright; simulate offline (page.context().setOffline(true)) and slow 3G in CI.
- Performance: Lighthouse CI with budgets (FCP, TTI, CLS, TBT) failing build if > threshold.
- Accessibility: axe-core automated + manual screen-reader pass every release.
Performance & Asset Strategy
- Code splitting and prefetching: use React.lazy + <link rel="prefetch" href="/next-chunk.js">.
- Images: serve AVIF/WebP first, fallback to JPEG via <picture>.
- Use Content-Security-Policy with script-src 'self' 'unsafe-inline'; connect-src https://api.example.com.
- HTTP/3 + Brotli compression enabled on edge/CDN; set Cache-Control: immutable,max-age=31536000 for hashed assets.
- Critical CSS extracted via @vitejs/plugin-critical or critters.
Security
- Enforce HTTPS via HSTS (max-age = 31536000; includeSubDomains; preload).
- Validate all service-worker messages (event.origin === self.origin && event.data?.type).
- Regularly rotate API keys and store secrets server-side only.
Accessibility
- Keyboard navigable, focus states visible > 3:1 contrast.
- Use semantic HTML; ARIA as last resort.
- Provide offline status indicator with aria-live="polite".
Analytics & Monitoring
- Use Google Analytics 4 or Plausible via service worker-/network-limited mode—send only when online.
- Collect Core Web Vitals in production using web-vitals library; report to backend for alerts.
Recommended Folder Structure
src/
components/
hooks/
pages/ (if Next.js)
services/
sw/ # service-worker source
types/
utils/
public/
images/
manifest.webmanifest
robots.txt
Commit & CI
- git hooks via Husky: lint-staged runs eslint --fix + prettier.
- commit msg format: Conventional Commits; semantic-release for versioning.
- GitHub Actions: test → build → lighthouse-ci → deploy.