Service-worker caching needs separate rules for reusable documents and query-driven data. A navigation to /app?utm_source=newsletter may need the same offline shell as /app, while /api/profile?user=bob must not reuse a response associated with another user.
Prerequisites: a service worker is already intercepting fetch events, the app has a known canonical shell path, and the shell has been stored in the intended cache. Before choosing a match option, decide whether the query string changes the response itself or merely records how the visitor reached the shell.
Make that decision before the cache lookup. Restrict shell reuse to GET requests for the explicit shell route, such as /app. For those requests only, choose matching that ignores the search part of the URL. This lets one cached document serve URL variants containing campaign tags or other parameters that do not affect the document.
For API calls, search pages, filters, reports, localized views, and user-specific endpoints, retain default exact matching. Their parameters commonly select the response, so treating every query as interchangeable creates a correctness risk. If the shell is not present in cache, use a network fallback rather than widening the matching rule. Non-GET requests should bypass the shell-cache path as well.
Expected result: navigations to the chosen shell path can resolve to one cached shell despite harmless search-text differences. Query-sensitive requests remain distinct, and mutations are not treated as cacheable navigations.
One limitation is important: ignoring search changes only the cache comparison of the URL query component. It does not make path aliases equivalent, decide which routes are safe to cache, or prove browser service-worker behavior from a policy-level fixture. Keep the eligible routes explicit and verify the real integration in a secure browser context.
Working example
// This module contains no service-worker side effects on import.
// Proof boundary: the route selection below is application code. A real browser
// service worker is still required to prove Cache.match()'s own cache semantics.
export const APP_SHELL_PATH = "/app";
export function isAppShellRequest(request) {
const url = new URL(request.url);
return request.method === "GET" && url.pathname === APP_SHELL_PATH;
}
/**
* Handles an already-intercepted service-worker fetch request.
*
* Only GET requests are eligible for cache lookup. Within that set, only the
* chosen app-shell route ignores its query string; API endpoints and other
* query-driven routes use Cache.match()'s default exact matching.
*/
export async function handleAppShellFetch(request, { cache, networkFetch }) {
// Cache.match() normally only matches GET and HEAD requests, but this policy
// intentionally does not involve the cache for any non-GET operation.
if (request.method !== "GET") {
return networkFetch(request);
}
const cached = isAppShellRequest(request)
? await cache.match(request, { ignoreSearch: true })
: await cache.match(request);
return cached ?? networkFetch(request);
}
/**
* Optional service-worker wiring. Call this from the service-worker script
* after opening the intended Cache instance.
*/
export function registerAppShellFetchHandler(scope, dependencies) {
scope.addEventListener("fetch", (event) => {
event.respondWith(handleAppShellFetch(event.request, dependencies));
});
}
Read the full article: https://chriseugenerodriguez.com/blog/cache-match-ignore-search-app-shell