↓ Skip to main content

Search still returned old results after deploy — it wasn't the service worker, it was max-age=600

·433 words·3 mins
Author
jeonck
Running a handful of Hugo/Docusaurus sites on GitHub Pages, and writing down the problems and fixes along the way.

Same day the service worker went live, a client-side search index gained new entries (news items, not just documents). The deploy succeeded, curl showed the new search-index.json on the server — and the user’s browser still said no results. The question arrived as “is the PWA breaking this?”

Why the worker was innocent
#

The worker’s fetch handler returns early for .json, so the browser handles those requests on its own. And the search page’s HTML is network-first, so the page itself was fresh. Nothing in the worker could hand back an old index.

What actually cached it
#

$ curl -sI https://<site>/search-index.json | grep -i cache
cache-control: max-age=600

GitHub Pages serves every file with a ten-minute max-age. The search page fetches /search-index.json by a fixed URL, so a browser that had opened search shortly before the deploy kept reusing its cached copy for up to ten minutes — no revalidation, no service worker involved. A hard reload fixed it, which is exactly the kind of “fix” that hides the cause.

The fix: version the URL from the HTML
#

The HTML is the one thing guaranteed fresh (network-first). So the build stamps the index URL into the page, and the script reads it from there instead of hard-coding the path.

<ul id="search-results" data-index="/search-index.json?v={{ build_ver }}"></ul>
pending = fetch(out.getAttribute('data-index') || '/search-index.json')

build_ver is the build timestamp — the same value the service worker uses for its cache name. Every deploy changes the query string; the HTTP cache treats it as a new resource.

The same trick had already been needed once that day for a different file: favicons. The browser had cached an old letter-mark icon under /assets/img/favicon.svg and never asked again, so the new city icon didn’t appear. There the version is a content hash of the SVG rather than a build timestamp, because the icon changes rarely and the URL should stay stable when it doesn’t.

The general rule
#

When a site with a service worker shows stale content, there are three caches in play, and only one of them is yours:

LayerWho controls itHow to bust it
Service worker cacheyoucache name per build; network-first for anything that must be fresh
Browser HTTP cacheserver headers (max-age)a version/hash in the URL, stamped from something that is fresh
CDN edgethe hostusually the same URL versioning; otherwise wait it out

Check which layer holds the old bytes before touching the worker. curl -sI on the file plus “does the URL change between builds?” answers it in a minute, and the answer was the middle row.