Skip to content

Instantly share code, notes, and snippets.

@fer-ri
Created August 21, 2026 03:35
Show Gist options
  • Select an option

  • Save fer-ri/a8812c9979f0959781d9cd1f4dd07dd7 to your computer and use it in GitHub Desktop.

Select an option

Save fer-ri/a8812c9979f0959781d9cd1f4dd07dd7 to your computer and use it in GitHub Desktop.
Serwist/Service-worker research for expense-tracker #32

Service worker + manifest options for Next.js 16 (research for #32)

Repo: apps/web, Next.js 16.3.0, App Router, default next build (Turbopack), PowerSync client already in place.

Options

1. Serwist — @serwist/next / @serwist/turbopack (recommended)

  • Actively maintained Workbox fork ("swiss army knife for service workers", 1.5k stars, release cadence active).
  • @serwist/next 9.5.12 (latest stable): peer next >=14.0.0; webpack-oriented integration (withSerwist wrapper + @serwist/webpack-plugin).
  • @serwist/turbopack (same 9.5.x line): dedicated Turbopack integration — withSerwist in next.config, a route handler app/serwist/[path]/route.ts that serves the SW, plus @serwist/turbopack/worker default cache strategies and SerwistProvider for registration. Built-in /~offline document fallback.
  • Serwist 10 (preview.14 on npm) targets Next 15+ only; if Next 16.3 surfaces breakage with the 9.5.x turbopack plugin, the 10 preview is the line to try (peer ranges must be checked then).
  • Turbopack is Next 16's default bundler for dev and build — the repo uses it implicitly (no webpack override in next.config.js). next-pwa (below) cannot work here without abandoning Turbopack.

2. next-pwa (reject)

  • 5.6.0, last published 4 years ago. Built on workbox-webpack-plugin; requires webpack build. Breaks with Turbopack; stale workbox internals. Effectively superseded — Serwist lists it as an alternative but was itself forked from the @ducanh2912/next-pwa continuation which is archived.

3. @ducanh2912/next-pwa (reject)

  • Maintainer moved on; archived; Serwist is its direct continuation. No reason to pick over Serwist.

4. Hand-rolled service worker (viable but worse)

  • Static public/sw.js + manual navigator.serviceWorker.register, workbox-free.
  • Cannot precache hashed .next/static assets (names unknown at authoring time) → precache only public/ + start URL; everything else runtime cache (stale-while-revalidate). First offline boot after a deploy can serve stale/missing chunks.
  • Full control, zero deps, but reinvents workbox caching semantics, no manifest injection, no /~offline convention. Fine for a pure-SPA shell; weaker for SSR'd Next.

Manifest

  • Manifest is orthogonal to SW choice: App Router native app/manifest.ts (metadata route) — supported since Next 14, works in 16. Icons in public/ + appleWebApp metadata + themeColor viewport export.

Integration sketch (recommended path)

npm i -D @serwist/turbopack esbuild serwist
next.config: export default withSerwist({ ...existing })
app/serwist/[path]/route.ts: createSerwistRoute({ swSrc: "app/sw.ts", additionalPrecacheEntries: [{ url: "/~offline", revision }] })
app/sw.ts: Serwist({ precacheEntries: self.__SW_MANIFEST, skipWaiting: true, clientsClaim: true, navigationPreload: true, runtimeCaching: defaultCache, fallbacks: { entries: [{ url: "/~offline", matcher: document }] } })
app/manifest.ts + public icons
app/layout.tsx: <SerwistProvider swUrl="/serwist/sw.js"> + metadata.appleWebApp + viewport.themeColor
app/~offline/route.tsx (or page): static offline fallback

Caching strategy (SSR app)

  • HTML (documents): network-first → cache (defaultCache does this; stale HTML off-the-wire is the risk, hence /~offline fallback).
  • Static chunks/CSS/fonts/images: precached from build manifest (hashed URLs → immutable, cache-first safe).
  • Exclude /api/* from runtime caching: auth routes (/api/auth/**, jose-signed cookies) and PowerSync HTTP endpoints must hit network; POST bodies are never cached by workbox defaults anyway.

PowerSync interplay

  • PowerSync runs as a web worker + IndexedDB; service workers do not intercept WebSocket connections or worker-scoped storage. No conflict expected.
  • Its own assets (public/@powersync/*) are plain static files → they'll be precached like any public/ file (small; fine).
  • Service worker must not precache anything user-specific — this SW is shell-only; all data stays in IndexedDB behind it. Cookie-gated proxy.ts means unauthenticated boot redirects to /sign-in — that page is runtime-cached like any other visited route, so a signed-out user still sees a (dead) login page offline; acceptable, UX ticket #33 owns the contract.

Risks / open items

  • Next 16.3 + @serwist/turbopack 9.5.x: peer says next >=14 but Next 16 is new — verify in a spike before committing; fallback line is Serwist 10 preview.
  • /~offline fallback only helps document requests; deep-linked uncached routes while offline fall back to it (by design).
  • SW update flow: skipWaiting: true + clientsClaim: true in template → new SW takes over on next load; stale-tab edge cases exist; UX ticket #33 owns the version/update story.

Recommendation

Serwist via @serwist/turbopack (9.5.x stable first, 10 preview as fallback), manifest via app/manifest.ts, offline page /~offline, /api excluded from caching. Cost: roughly half a day incl. icons + offline testing; 3 dev deps, ~6 small files, no build pipeline changes (Turbopack stays default). Hand-rolled only if the Serwist/Next-16 pairing breaks and dependency-averse wins — not recommended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment