add-pwa
Setup Progressive Web App (PWA) support for a Next.js project using Serwist. Use when user says "setup pwa", "add pwa", "make it a pwa", "progressive web app", or "enable offline support".
What this skill does
# Add PWA Support
Adds Progressive Web App capabilities to a Next.js project using [`@serwist/turbopack`](https://serwist.pages.dev/docs/next/turbo) โ the Turbopack-compatible Serwist integration. Uses esbuild via a Route Handler to bundle the service worker, so **no webpack fallback is needed**. Provides installability, offline support, precaching, and runtime caching strategies out of the box.
## What Gets Created
| File | Purpose |
|------|---------|
| `app/manifest.ts` | Web app manifest (name, icons, theme) |
| `app/sw.ts` | Service worker with precaching and offline fallback |
| `app/serwist/[path]/route.ts` | Route handler that bundles and serves the SW via esbuild |
| `app/serwist-provider.tsx` | Re-exports `SerwistProvider` with `"use client"` directive |
| `app/~offline/page.tsx` | Offline fallback page |
| `components/pwa-install-prompt.tsx` | Install prompt banner component |
| `components/pwa-provider.tsx` | PWA context provider (online/offline status) |
| `scripts/generate-icons.ts` | Placeholder icon generator |
| `public/icons/icon-192x192.svg` | Android/Chrome app icon (placeholder) |
| `public/icons/icon-512x512.svg` | Android/Chrome splash icon (placeholder) |
| `public/icons/apple-touch-icon.svg` | iOS home screen icon (placeholder) |
## Prerequisites
- Next.js 15+ project (App Router)
- TypeScript configured
## Installation
```bash
bun add -D @serwist/turbopack esbuild serwist
```
## Setup Steps
### 1. Create Web App Manifest
Create `app/manifest.ts`:
```ts
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "My App",
short_name: "MyApp",
description: "A Progressive Web App built with Next.js",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#000000",
orientation: "portrait",
icons: [
{
src: "/icons/icon-192x192.svg",
sizes: "192x192",
type: "image/svg+xml",
purpose: "maskable",
},
{
src: "/icons/icon-512x512.svg",
sizes: "512x512",
type: "image/svg+xml",
},
],
};
}
```
**Note:** Next.js automatically serves this at `/manifest.webmanifest` and adds the `<link rel="manifest">` tag. No manual `<link>` tag needed. Replace SVG placeholders with real PNG icons for production.
### 2. Create Service Worker
Create `app/sw.ts`:
```ts
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import { defaultCache } from "@serwist/turbopack/worker";
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
import { Serwist } from "serwist";
declare global {
interface WorkerGlobalScope extends SerwistGlobalConfig {
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
}
}
declare const self: ServiceWorkerGlobalScope;
const serwist = new Serwist({
precacheEntries: self.__SW_MANIFEST,
skipWaiting: true,
clientsClaim: true,
navigationPreload: true,
runtimeCaching: defaultCache,
fallbacks: {
entries: [
{
url: "/~offline",
matcher({ request }) {
return request.destination === "document";
},
},
],
},
});
serwist.addEventListeners();
```
**Important:** The `/// <reference no-default-lib="true" />` directive removes default type definitions (including `dom`) from the compilation scope. You **must** exclude this file from the main TypeScript compilation. Add `"app/sw.ts"` to the `exclude` array in `tsconfig.json`:
```jsonc
{
"exclude": ["node_modules", "app/sw.ts"]
}
```
This is safe because `app/sw.ts` is bundled separately by esbuild via the route handler โ it does not go through the normal `tsc` / Next.js compilation pipeline.
### 3. Create Serwist Route Handler
Create `app/serwist/[path]/route.ts`:
```ts
import { spawnSync } from "node:child_process";
import { createSerwistRoute } from "@serwist/turbopack";
const revision =
spawnSync("git", ["rev-parse", "HEAD"], { encoding: "utf-8" }).stdout.trim() ||
crypto.randomUUID();
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } =
createSerwistRoute({
additionalPrecacheEntries: [{ url: "/~offline", revision }],
swSrc: "app/sw.ts", // Use "src/app/sw.ts" if project has --src-dir
useNativeEsbuild: true,
});
```
This route handler uses esbuild to bundle `app/sw.ts` on-demand, injecting the precache manifest. The compiled service worker is served at `/serwist/sw.js`.
### 4. Create Offline Fallback Page
Create `app/~offline/page.tsx`:
```tsx
export default function OfflinePage() {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<div className="text-center px-6">
<div className="text-6xl mb-6">๐ก</div>
<h1 className="text-2xl font-bold text-foreground mb-2">
You're offline
</h1>
<p className="text-muted-foreground max-w-sm">
Check your internet connection and try again. Some features may still
be available offline.
</p>
</div>
</div>
);
}
```
### 5. Configure next.config.ts
Update `next.config.ts` to wrap with Serwist:
```ts
import { withSerwist } from "@serwist/turbopack";
export default withSerwist({
// Your existing Next.js config goes here
});
```
**Important:** If the project has an existing `next.config.ts` with other config (e.g. `reactCompiler: true`), pass it inside the `withSerwist()` call:
```ts
import { withSerwist } from "@serwist/turbopack";
export default withSerwist({
reactCompiler: true,
// ...other config
});
```
### 6. Create SerwistProvider Re-export
Create `app/serwist-provider.tsx`:
```tsx
"use client";
export { SerwistProvider } from "@serwist/turbopack/react";
```
Serwist's `SerwistProvider` must be used in a client component. This re-export adds the `"use client"` directive.
### 7. Update Root Layout Metadata & Wire Up Provider
Update `app/layout.tsx` to include PWA metadata and the Serwist provider:
```tsx
import type { Metadata, Viewport } from "next";
import { SerwistProvider } from "./serwist-provider";
import { PWAInstallPrompt } from "@/components/pwa-install-prompt";
import { PWAProvider } from "@/components/pwa-provider";
export const metadata: Metadata = {
applicationName: "My App",
title: {
default: "My App",
template: "%s | My App",
},
description: "A Progressive Web App built with Next.js",
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "My App",
},
formatDetection: {
telephone: false,
},
};
export const viewport: Viewport = {
themeColor: "#000000",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SerwistProvider swUrl="/serwist/sw.js">
<PWAProvider>
{children}
<PWAInstallPrompt />
</PWAProvider>
</SerwistProvider>
</body>
</html>
);
}
```
**Note:** `themeColor` must be in the `viewport` export, not `metadata` (changed in Next.js 14+). The `swUrl` points to the route handler path, not a static file.
### 8. Generate Placeholder Icons
Create `scripts/generate-icons.ts`:
```ts
import { mkdirSync, writeFileSync } from "node:fs";
function generatePlaceholderSVG(size: number): string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
<rect width="${size}" height="${size}" fill="#000000" rx="${size * 0.1}"/>
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" fill="white" font-family="system-ui" font-size="${size * 0.4}" font-weight="bold">App</text>
</svg>`;
}
mkdirSync("public/icons", { recursive: true });
for (const size of [192, 512]) {
writeFileSync(
`public/icons/icon-${size}x${size}.svg`,
generatePlaceholderSVG(size),
);
}
writeFileSync("public/icons/apple-touch-icRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4โv5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.