Guide for modifying service worker caching. Edit sw-template.js (not sw.js), run yarn prebuild after changes.
Guide for modifying the service worker caching strategy. Prevent common mistakes that break offline functionality.
The public/sw.js file is generated and will be overwritten.
Always edit: public/sw-template.js
public/sw-template.js โ scripts/generate-sw.mjs โ public/sw.js
(source) (build script) (generated)
The build script:
sw-template.js{{API_ORIGIN}} placeholder with NEXT_PUBLIC_API_URL originpublic/sw.jsRun yarn prebuild after changing sw-template.js:
# After editing sw-template.js
yarn prebuild
# Or just run dev (includes prebuild)
yarn dev
Note: yarn build alone does NOT run prebuild. Environment-specific builds (build:development|staging|production) do include it.
// public/sw-template.js
const API_ORIGIN = "{{API_ORIGIN}}"; // Replaced at build time
// Workbox 7 configuration
importScripts(
"https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox-sw.js"
);
const { registerRoute } = workbox.routing;
const { CacheFirst, NetworkFirst, StaleWhileRevalidate } = workbox.strategies;
// Cache strategies for different routes
registerRoute(
({ url }) => url.origin === API_ORIGIN && url.pathname.startsWith("/api/"),
new NetworkFirst({ cacheName: "api-cache" })
);
| Strategy | Use Case | Example |
|---|---|---|
CacheFirst |
Static assets, images | Fonts, icons |
NetworkFirst |
API calls, dynamic data | Events, news |
StaleWhileRevalidate |
Balance freshness/speed | Category lists |
// public/sw-template.js
// Add new route
registerRoute(
({ url }) => url.pathname.startsWith("/new-feature/"),
new StaleWhileRevalidate({
cacheName: "new-feature-cache",
matchOptions: { ignoreVary: true }, // REQUIRED for Next.js App Router
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 24 * 60 * 60, // 24 hours
}),
],
})
);
yarn prebuild
yarn dev
# Check DevTools > Application > Service Workers
registerRoute(
({ url }) => url.origin === API_ORIGIN && url.pathname.includes("/events"),
new NetworkFirst({
cacheName: "events-cache",
networkTimeoutSeconds: 3,
matchOptions: { ignoreVary: true }, // REQUIRED for Next.js App Router
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 60 * 60, // 1 hour
}),
],
})
);
registerRoute(
({ request }) => request.destination === "image",
new CacheFirst({
cacheName: "images-cache",
matchOptions: { ignoreVary: true }, // REQUIRED for Next.js App Router
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
}),
],
})
);
// Offline page fallback
workbox.routing.setCatchHandler(async ({ event }) => {
if (event.request.destination === "document") {
return caches.match("/offline");
}
return Response.error();
});
The {{API_ORIGIN}} placeholder is replaced with the origin from NEXT_PUBLIC_API_URL:
# .env
NEXT_PUBLIC_API_URL=https://api.example.com/v1
# Results in sw.js:
const API_ORIGIN = "https://api.example.com";
ignoreVary: trueNext.js App Router adds Vary headers (rsc, next-router-state-tree, next-router-prefetch, etc.) to responses. Without ignoreVary: true, service worker cache matching fails because these headers differ between request types (RSC navigation vs client fetch vs direct load).
Note: This addresses Workbox service worker cache matching, not the underlying Next.js dynamic route issue (which relates to Cache-Control: private/no-store when Server Components call headers() or cookies()). The ignoreVary: true option allows the SW to treat the same URL as one cache entry regardless of Vary header differences.
Always add matchOptions with ignoreVary: true:
new workbox.strategies.StaleWhileRevalidate({
cacheName: "my-cache",
matchOptions: {
ignoreVary: true, // REQUIRED for Next.js App Router compatibility
},
plugins: [
/* ... */
],
});
Without this, the same URL may be fetched multiple times and cached as separate entries, causing:
ignoreVary: true โ Cache misses due to Next.js Vary headerssw-template.js (not sw.js)?yarn prebuild after changes?matchOptions: { ignoreVary: true } to strategies?