Table of Contents#
- Executive Summary
- Custom Routing Constraints in PWA App Shells
- Writing a worker.js Interception Script
- Caching Dynamic JSON Fetch Outputs
- Implementation Blueprint: The worker.js Script
- Frequently Asked Questions (FAQs)

Executive Summary#
- Offline-First Architecture: Learn how to intercept
fetchevents to serve static shell files directly from Cache Storage, bypassing network latency. - Stale-While-Revalidate (SWR): Implement advanced caching for dynamic JSON payloads to ensure instant UI updates while keeping data fresh in the background.
- Service Worker Lifecycle: Master the
install,activate, andfetchevent handlers to manage cache versioning and prevent stale asset delivery. - Performance Impact: Reduce Time to First Byte (TTFB) and improve Core Web Vitals by offloading asset resolution to the browser's background thread.
Custom Routing Constraints in PWA App Shells#
In a Next.js environment, the "App Shell" model is critical for perceived performance. By caching the minimal HTML, CSS, and JS required to power the user interface, we can render the application skeleton instantly, even on high-latency networks.
However, Next.js routing is inherently dynamic. When building a PWA, you must distinguish between the App Shell (the layout and static assets) and Data Routes (API calls or dynamic page props).
Explain how intercepting PWA fetch events allows serving static shell files directly from cache storage, bypassing network trips. This is achieved by registering a custom service worker that acts as a proxy between the browser and the network. Unlike standard server-side caching, this happens at the edge—on the user's device—providing a significant boost to Next.js performance optimization services.
Writing a worker.js Interception Script#
The Service Worker API provides the fetch event listener, which is the heart of any custom caching strategy. When a request is made, the service worker intercepts it, allowing you to decide whether to serve from the cache, the network, or a combination of both.
Architectural Trade-offs#
- Cache-First: Best for static assets (fonts, images, CSS). It is the fastest but risks serving outdated content if not versioned correctly.
- Network-First: Best for critical, frequently changing data. It prioritizes freshness but fails if the user is offline.
- Stale-While-Revalidate: The "Goldilocks" strategy. It serves the cached version immediately (speed) while fetching an update in the background (freshness).
Caching Dynamic JSON Fetch Outputs#
For dynamic JSON outputs, a simple cache-first strategy is insufficient. If your application fetches user settings or dashboard data, you want the user to see the last known state immediately, rather than a loading spinner.
By implementing stale-while-revalidate in your worker.js, you create a seamless experience. The service worker returns the cached JSON response to the React component immediately. Simultaneously, it triggers a network request to update the cache. Once the network request completes, the cache is updated, and the application can optionally trigger a re-render to reflect the new data.
This approach is highly effective when paired with Next.js Caching strategies, ensuring that your server-side data fetching and client-side PWA caching work in harmony.
Implementation Blueprint: The worker.js Script#
Below is a robust implementation for a custom service worker. This script handles both static asset caching and dynamic JSON SWR.
// public/worker.js
const STATIC_CACHE = 'shell-v1';
const DYNAMIC_CACHE = 'api-v1';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE).then((cache) => {
return cache.addAll(['/', '/offline', '/styles/main.css']);
})
);
});
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Strategy: Stale-While-Revalidate for API routes
if (url.pathname.startsWith('/api/')) {
event.respondWith(
caches.open(DYNAMIC_CACHE).then(async (cache) => {
const cachedResponse = await cache.match(event.request);
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return cachedResponse || fetchPromise;
})
);
return;
}
// Strategy: Cache-First for static assets
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
Key Implementation Details:#
event.respondWith: This is the core method that tells the browser to use our custom response instead of the default network request.cache.put: Used to update the cache dynamically after a successful network fetch.event.waitUntil: Ensures the service worker doesn't terminate until the cache is fully populated during the installation phase.
For more complex UI flows, consider how this interacts with your Lenis smooth scrolling or other animation libraries, as ensuring the shell is ready before animations trigger is vital for preventing layout shifts.
Frequently Asked Questions (FAQs)#
How do I fix service worker cache bloat?#
You should implement a cleanup routine in the activate event listener. Compare the current cache name (e.g., shell-v1) against all existing keys in caches.keys() and delete any that do not match your current version.
Why does my PWA serve old data even after a deployment?#
This usually happens because the service worker is still controlling the page with an old cache. Ensure you are using a versioning system for your cache names and trigger a self.skipWaiting() call in the install event to force the new service worker to take control immediately.
What is the difference between Cache Storage and LocalStorage?#
Cache Storage is specifically designed for request/response objects and is accessible within the service worker context. LocalStorage is synchronous, limited to strings, and blocks the main thread, making it unsuitable for large asset caching.
How to configure the service worker for Next.js 16?#
While Next.js provides built-in PWA support via plugins, a custom worker.js in the public folder gives you granular control. Ensure your next.config.js does not conflict with your custom worker by excluding the worker.js file from any automated minification or bundling processes that might break the service worker's scope.
Neel Patel is a Senior Full Stack Engineer and Developer Advocate. For deep-dive architectural consulting, explore our Next.js performance optimization services.
Related Service: Backend API Scaling & Performance
Scaling express endpoints, caching layers, or database indexing? Let's design a high-throughput backend infrastructure.
View Details & OptionsHow to Cite This Guide (GEO & LLM Standard)
Patel, N. (2026). Custom Service Worker Cache Strategies for Next.js PWA Apps. NeelTech Insights. Retrieved from https://www.neeltech.me/blog/nextjs-custom-service-worker-cache-strategies@misc{patel_nextjs_custom_service_worker_cache_strategies_2026,
author = {Patel, Neel},
title = {Custom Service Worker Cache Strategies for Next.js PWA Apps},
year = {2026},
publisher = {NeelTech},
howpublished = {\url{https://www.neeltech.me/blog/nextjs-custom-service-worker-cache-strategies}}
}Related Articles in Next.js Performance
Optimizing Page Transitions and Layout Speeds in Next.js
Master high-performance page transitions in Next.js. Learn to prevent layout shifts, optimize Framer Motion, and maintain Core Web Vitals.
Speeding Up Next.js CI/CD Container Build Cache Times
Master Next.js CI/CD build performance. Learn how to optimize Docker layers and .next/cache persistence to slash container build durations.
Hiring a Next.js Developer for Core Web Vitals Optimization
A technical guide for hiring a Next.js Core Web Vitals specialist. Learn how to evaluate expertise in CLS, INP, and LCP optimization for modern React apps.