Build PWAs with service workers, offline support, and app-like experience
# Progressive Web Apps (PWA) Development Guide
Build modern Progressive Web Apps with service workers, offline capabilities, and native-like experiences using Google Antigravity IDE for optimal development workflow.
## Service Worker Setup
### Registration and Lifecycle
```typescript
// src/service-worker.ts
/// <reference lib="webworker" />
declare const self: ServiceWorkerGlobalScope;
const CACHE_NAME = "app-cache-v1";
const STATIC_ASSETS = [
"/",
"/index.html",
"/styles/main.css",
"/scripts/app.js",
"/images/logo.png",
"/offline.html",
];
// Install event - cache static assets
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log("Caching static assets");
return cache.addAll(STATIC_ASSETS);
})
);
// Activate immediately
self.skipWaiting();
});
// Activate event - clean old caches
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
// Take control of all clients
self.clients.claim();
});
// Fetch event - serve from cache with network fallback
self.addEventListener("fetch", (event) => {
const { request } = event;
// Skip non-GET requests
if (request.method !== "GET") return;
// Handle API requests differently
if (request.url.includes("/api/")) {
event.respondWith(networkFirst(request));
} else {
event.respondWith(cacheFirst(request));
}
});
async function cacheFirst(request: Request): Promise<Response> {
const cached = await caches.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch {
return caches.match("/offline.html") as Promise<Response>;
}
}
async function networkFirst(request: Request): Promise<Response> {
try {
const response = await fetch(request);
if (response.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
}
return response;
} catch {
const cached = await caches.match(request);
return cached || new Response(JSON.stringify({ error: "Offline" }), {
status: 503,
headers: { "Content-Type": "application/json" },
});
}
}
```
### Registration in App
```typescript
// src/registerSW.ts
export async function registerServiceWorker() {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
});
registration.addEventListener("updatefound", () => {
const newWorker = registration.installing;
newWorker?.addEventListener("statechange", () => {
if (newWorker.state === "installed" && navigator.serviceWorker.controller) {
// New version available
dispatchEvent(new CustomEvent("sw-update-available"));
}
});
});
console.log("SW registered:", registration.scope);
} catch (error) {
console.error("SW registration failed:", error);
}
}
}
```
## Web App Manifest
```json
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A fast, reliable progressive web application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#3b82f6",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
],
"shortcuts": [
{
"name": "New Document",
"url": "/new",
"icons": [{ "src": "/icons/new.png", "sizes": "96x96" }]
}
]
}
```
## Push Notifications
```typescript
// src/pushNotifications.ts
export async function subscribeToPush(): Promise<PushSubscription | null> {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
});
// Send subscription to backend
await fetch("/api/push/subscribe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(subscription),
});
return subscription;
}
function urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}
```
## Background Sync
```typescript
// In service worker
self.addEventListener("sync", (event) => {
if (event.tag === "sync-messages") {
event.waitUntil(syncMessages());
}
});
async function syncMessages() {
const db = await openDB("app-db", 1);
const pendingMessages = await db.getAll("pending-messages");
for (const message of pendingMessages) {
try {
await fetch("/api/messages", {
method: "POST",
body: JSON.stringify(message),
});
await db.delete("pending-messages", message.id);
} catch {
// Will retry on next sync
}
}
}
```
This PWA guide provides offline-first architecture with service workers, push notifications, and background sync for native-like web experiences.This PWA prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their pwa implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For PWA projects, consider mentioning your framework version, coding style, and any specific libraries you're using.