pwa-review

star 7

Comprehensive 192-point PWA audit beyond Lighthouse - analyzes manifest, service worker, offline capabilities, real-time resilience, security, iOS compatibility, and advanced PWA features

emrahub By emrahub schedule Updated 2/28/2026

name: pwa-review description: Comprehensive 192-point PWA audit beyond Lighthouse - analyzes manifest, service worker, offline capabilities, real-time resilience, security, iOS compatibility, and advanced PWA features user_invocable: true args:


PWA Review Skill

A comprehensive Progressive Web App audit that goes beyond standard Lighthouse testing. This skill analyzes PWAs across 11 categories with a 192-point scoring system, including real-time connection resilience, advanced features, and iOS-specific compatibility checks that typical audits miss.

Scoring Overview

Category Points Focus
Manifest Compliance 20 Essential manifest fields
Advanced Manifest 15 Enhanced manifest features + iOS splash
Service Worker & Caching 33 SW implementation quality + caching strategies
Offline Capability 24 Offline functionality + storage + sync + real-time resilience
Installability 13 Install requirements
Security 16 Security measures
Performance Signals 17 Performance optimization + network detection
UX & Accessibility 29 User experience + iOS safe areas + mobile dropdowns + themes + state persistence
SEO & Discoverability 7 Search optimization
PWA Advanced 17 Cutting-edge PWA features
iOS Compatibility 1 iOS-specific meta tags (bonus)

Grading Scale: A+ (173+, 90%+), A (154-172, 80-89%), B (135-153, 70-79%), C (116-134, 60-69%), D (77-115, 40-59%), F (<77, <40%)


Execution Workflow

When the user invokes /pwa-review <url>, follow these steps precisely:

Step 1: Fetch Target HTML

Use WebFetch to retrieve the target URL's HTML content.

WebFetch: {url}
Prompt: "Return the complete HTML source code. I need to analyze the head section for PWA-related tags including manifest link, meta tags, and inline scripts."

Step 2: Extract PWA Resources

From the HTML, identify:

Manifest URL:

  • Look for <link rel="manifest" href="...">
  • Convert relative URLs to absolute using the base URL
  • If not found, note as CRITICAL issue

Service Worker Registration:

  • Search for navigator.serviceWorker.register('...') or navigator.serviceWorker.register("...")
  • Extract the SW file path
  • If not found, note as CRITICAL issue

Meta Tags to Extract:

  • <meta name="theme-color" content="...">
  • <meta name="apple-mobile-web-app-capable" content="...">
  • <meta name="apple-mobile-web-app-status-bar-style" content="...">
  • <meta name="mobile-web-app-capable" content="...">
  • <meta name="viewport" content="..."> (check for viewport-fit=cover)
  • <meta http-equiv="Content-Security-Policy" content="...">
  • <link rel="apple-touch-icon" href="...">
  • <link rel="apple-touch-startup-image" ...> (iOS splash screens)

Step 3: Fetch Manifest

If manifest URL was found, use WebFetch to retrieve it:

WebFetch: {manifest_url}
Prompt: "Return the complete manifest.json content as raw JSON."

If manifest fetch fails (CORS, 404, etc.), score manifest categories as 0 and continue.

Step 4: Fetch Service Worker

If service worker URL was found, use WebFetch to retrieve it:

WebFetch: {sw_url}
Prompt: "Return the complete service worker JavaScript code."

If SW fetch fails, score SW-related categories as 0 and continue.

Step 5: Analyze & Score

Evaluate each category using the detailed checklist below. Track:

  • Passed items (full points)
  • Failed items (0 points) - record as issues
  • Partial items (partial points where applicable)

Step 6: Generate Report

Output a markdown report following the template at the end of this document.


Detailed Scoring Checklist

Category 1: Manifest Compliance (20 points)

Check Points How to Verify
name field present and non-empty 2 manifest.name exists and length > 0
short_name present (≤12 chars recommended) 2 manifest.short_name exists
icons array with 192x192 PNG 4 icons array has item with sizes="192x192"
icons array with 512x512 PNG 4 icons array has item with sizes="512x512"
start_url defined 2 manifest.start_url exists
display mode set (standalone/fullscreen/minimal-ui) 2 manifest.display is one of allowed values
background_color specified 2 manifest.background_color exists (hex/rgb/named)
theme_color specified 2 manifest.theme_color exists

Critical Blocker: If manifest is missing entirely, this category scores 0/20.

Category 2: Advanced Manifest Features (15 points)

Check Points How to Verify
description field present 1 manifest.description exists
screenshots array for install UI 2 manifest.screenshots array with ≥1 item
shortcuts array for quick actions 2 manifest.shortcuts array with ≥1 item
categories array defined 1 manifest.categories exists
orientation preference set 1 manifest.orientation exists
dir and lang for i18n 1 manifest.dir OR manifest.lang exists
id field for app identity 1 manifest.id exists
scope properly defined 1 manifest.scope exists
Maskable icon present 1 icons array has item with purpose="maskable" or "any maskable"
note_taking object 1 manifest.note_taking exists (ChromeOS lock screen notes)
widgets array 1 manifest.widgets exists (Windows 11 Widgets Board)
iOS splash screens present 2 <link rel="apple-touch-startup-image"> tags for multiple device sizes

iOS Splash Screen Note: iOS requires separate <link rel="apple-touch-startup-image"> tags for each device size. Without these, iOS shows a blank white screen during PWA launch. Check for multiple media queries covering different device dimensions.

Category 3: Service Worker & Caching (33 points)

Check Points How to Verify
SW registered in HTML 2 navigator.serviceWorker.register() found
install event handler present 3 SW contains addEventListener('install', ...) or self.oninstall
activate event handler present 3 SW contains addEventListener('activate', ...) or self.onactivate
fetch event handler present 4 SW contains addEventListener('fetch', ...) or self.onfetch
Cache API usage (caches.open/put/match) 3 SW contains caches.open or cache.put or cache.match
Cache versioning/naming strategy 2 SW has cache name variable (CACHE_NAME, CACHE_VERSION, etc.)
Old cache cleanup in activate 2 activate handler deletes old caches
Background Sync support 2 SW contains addEventListener('sync', ...) or addEventListener('periodicsync', ...)
Workbox usage (bonus, not required) 1 SW imports workbox or uses workbox.* methods
skipWaiting() usage 1 SW contains self.skipWaiting() for instant activation
clients.claim() usage 1 SW contains clients.claim() for immediate control
Navigation preload 1 SW uses navigationPreload.enable()
Stale-while-revalidate pattern 1 fetch handler serves cache then updates in background
Push event handler 1 SW contains addEventListener('push', ...)
notificationclick handler 1 SW contains addEventListener('notificationclick', ...)
Notification action buttons 1 Push notifications include actions array OR notificationclick checks event.action
Multiple caching strategies 2 SW uses different strategies for different routes (CacheFirst, NetworkFirst, StaleWhileRevalidate)
Cache expiration config 1 SW has maxEntries or maxAgeSeconds for cache pruning
SW message handler 1 SW contains addEventListener('message', ...) for client communication

Critical Blocker: If no service worker, this category scores 0/33.

Caching Strategies Note: Production-grade PWAs should use different caching strategies based on resource type:

  • CacheFirst: Static assets, fonts, images (rarely change)
  • NetworkFirst: API responses, dynamic content (freshness matters)
  • StaleWhileRevalidate: JS/CSS bundles (speed + freshness balance) Look for patterns like new CacheFirst(), new NetworkFirst(), or explicit strategy patterns in fetch handlers.

Cache Expiration Note: Without expiration limits, caches grow unbounded and can exceed storage quotas. Look for ExpirationPlugin with maxEntries or maxAgeSeconds, or custom cleanup logic in the fetch handler.

Category 4: Offline Capability (24 points)

Check Points How to Verify
Offline fallback page defined 3 SW caches and serves an offline.html or similar
App shell resources precached 3 install event caches core HTML/CSS/JS files
Offline indicator in UI (code pattern) 2 Code checks navigator.onLine or listens to online/offline events
Network-first or cache-first strategy evident 2 fetch handler has clear strategy pattern
Update prompt shown to user 1 Code handles SW update with user notification (e.g., "New version available")
Graceful update flow 1 Update doesn't force reload without warning, user can choose when to update
Update state persistence 1 localStorage flag prevents update prompt re-appearing after update (e.g., pwa-just-updated)
Touch event double-fire prevention 1 Update/action handlers prevent duplicate execution from onClick + onTouchEnd
Persistent storage request 1 Code uses navigator.storage.persist() to prevent iOS data eviction
IndexedDB offline storage 1 Code uses indexedDB.open() or idb library for structured offline data
Storage quota monitoring 1 Code uses navigator.storage.estimate() for storage health checks
Background sync client trigger 1 Client triggers registration.sync.register() when coming back online
Periodic sync registration 1 Client registers registration.periodicSync.register() on app init
Visibility change reconnection 2 Code listens to visibilitychange event and triggers WebSocket/real-time reconnection when page becomes visible
Real-time reconnection strategy 2 WebSocket/Socket.io configured with sufficient reconnection attempts (>5 or Infinity) and progressive backoff
Connection room re-subscribe 1 After real-time reconnection, rooms/channels are re-joined automatically (not only on initial connect)

Update UX Note: Good PWAs notify users when updates are available and let them choose when to apply the update. Look for patterns like useRegisterSW, workbox-window, or custom SW update handling with user-facing notifications.

Background Sync Client Trigger Note: Having a sync event handler in the service worker is not enough. The client must explicitly trigger background sync when coming back online by calling navigator.serviceWorker.ready.then(reg => reg.sync.register('sync-pending-requests')) in the online event listener. Without this, offline requests remain queued indefinitely.

Periodic Sync Registration Note: The service worker periodicsync event handler must be complemented by client-side registration during app initialization. Look for registration.periodicSync.register('sync-content', { minInterval: ... }) wrapped in a permission check (navigator.permissions.query({ name: 'periodic-background-sync' })). This enables automatic background content updates even when the app is closed.

Update State Note: After a user clicks "Update", the PWA reloads. Without state management, the update prompt may immediately re-appear because the new service worker is still "waiting". Use localStorage flags (e.g., pwa-just-updated with timestamp) to suppress the prompt for a brief period (30 seconds) after update completion. Also implement double-fire prevention for touch handlers - on iOS, both onClick and onTouchEnd may fire, causing duplicate updates.

Offline Storage Note: For complex PWAs with user-generated content, localStorage alone is insufficient. Use IndexedDB for structured data storage (images, generation history, preferences). Request persistent storage with navigator.storage.persist() to prevent iOS from evicting data after 7 days of inactivity. Monitor storage quota with navigator.storage.estimate() to warn users before running out of space.

Visibility Change Reconnection Note: When a PWA is minimized, tab-switched, or the user navigates to another app on mobile, WebSocket/real-time connections silently die. The browser fires visibilitychange when the user returns, but most apps don't listen for it. Without a visibilitychange handler that checks connection state and triggers reconnection, users return to a permanently disconnected app. This is especially critical on iOS where PWAs are effectively frozen when backgrounded. Look for:

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && !socket.connected) {
    socket.connect(); // or custom reconnect logic
  }
});

Real-Time Reconnection Strategy Note: Socket.io and similar libraries default to limited reconnection attempts (e.g., 5 attempts). In a PWA context where users may background the app for hours, 5 attempts are exhausted in ~15 seconds — long before the user returns. Production PWAs should configure reconnectionAttempts: Infinity with a reasonable reconnectionDelayMax (e.g., 30 seconds) for progressive backoff. Also provide a manual retry mechanism in the UI so users can trigger immediate reconnection. Look for:

  • reconnectionAttempts configuration (should be high or Infinity)
  • reconnectionDelayMax configuration (should be >5000ms)
  • Manual reconnect button or tap-to-retry UI
  • reconnect_failed event handling

Connection Room Re-Subscribe Note: After a WebSocket reconnection, the server-side room/channel memberships are lost. If the app only joins rooms on initial connection (e.g., in a useEffect([workspaceId]) that doesn't re-trigger on reconnect), real-time events stop flowing after reconnection. The fix is to listen for the connect event persistently and re-join rooms on every reconnection — not just the first connection. Look for patterns where room-joining logic is called inside a persistent connect event listener rather than a self-removing one.

Category 5: Installability Requirements (13 points)

Check Points How to Verify
Served over HTTPS 3 URL starts with https://
Valid manifest linked in HTML 2 exists with valid href
Service worker with fetch handler 2 Covered in SW category, cross-check
192x192 icon present 1 Covered in manifest, cross-check
512x512 icon present 1 Covered in manifest, cross-check
apple-touch-icon for iOS 1 in HTML
beforeinstallprompt handled 2 HTML/JS contains beforeinstallprompt event listener
Custom install UI 1 Code shows/hides custom install button

Note: prefer_related_applications: true in manifest BLOCKS browser install prompt - flag as CRITICAL if found.

Category 6: Security Measures (16 points)

Check Points How to Verify
HTTPS enforced 2 URL is https:// (duplicate check for emphasis)
Content Security Policy present 3 CSP meta tag or mention in SW/HTML
Subresource Integrity (SRI) on scripts 2