Service Worker Held My Site Hostage: How I Removed It
How a single cached file made my entire GitHub Pages site refuse to update, why it happened, and exactly how to fix it if it ever happens to you. Service workers are a browser API designed for offline-first web apps (MDN: Service Worker API), but when left unmanaged they can serve stale content indefinitely.
What actually happened
My GitHub Pages frontend absolutely refused to update after a full rebuild. I deleted the old code, pushed a new Next.js build, cleaned every folder, checked the deploy logs, and refreshed the live site a hundred times. The same old UI kept loading. Same filters button. Same old DOM. It looked like my deploy did not even run. GitHub Pages serves static content directly from a repository branch (GitHub Pages documentation), so a clean deploy should mean a clean site.
GitHub Actions passed every time. The artifact had the correct static export. My repo was clean. My build folder was clean. I even ran grep across the entire project looking for any leftover UI code and found nothing. GitHub Actions workflows run in isolated runners and produce artifacts you can inspect (GitHub Actions documentation), so I could confirm the build output was correct.
Then I hit the live URL with curl and it returned the correct new markup. But the browser still showed the old one. So at that point the server and the browser disagreed. When that happens, something in the middle is lying to you.
And that something was a service worker I did not even know existed.
What the actual root problem was
A stale service worker was still registered in the browser from an earlier version of the site. I did not write the service worker myself. AI generated it once, and I never questioned it. I did not know what "sw.js" even meant at the time.
A service worker sits between the browser and the network. It can intercept requests and decide what to return (MDN: Using Service Workers). My service worker had cached the old version of the site and never let go of it. So even though the server was sending the new version, the browser ignored it.
Service workers have a lifecycle with distinct phases — registration, installation, activation, and termination (MDN: Service Worker Lifecycle). The key problem is that an activated service worker persists across browser sessions and continues intercepting requests even after you navigate away and come back. Unless you explicitly unregister it or the service worker code itself calls self.skipWaiting() combined with a cache-busting strategy, the old cached content wins every time.
The browser trusted the service worker more than the server.
This is the exact kind of bug that does not feel logical until you understand how aggressive service worker caching is.
The fix that finally worked
Here is the fix that instantly solved it:
- Open your site in Chrome.
- Open DevTools with
Ctrl + Shift + IorCmd + Option + I(Chrome DevTools documentation). - Go to the Application tab.
- Click Service Workers on the left.
- You will see one or more registered service workers. Look for anything like
sw.jsorservice-worker.js, or anything showing “activated.” - Click Unregister.
- Refresh the page.
If the service worker was the problem, the new version of the site will load instantly. No delay. No waiting. It is one of the most dramatic “instant fix” moments you can have in web development.
Comparison of debugging methods
When your site refuses to update, there are three fast ways to narrow down the cause. Here is how they compare:
| Method | What it checks | Speed | What a positive result means |
|---|---|---|---|
curl the live URL | Server-side HTML output | Seconds | If curl shows new HTML but the browser does not, the problem is client-side caching |
| Open in Incognito | Fresh browser session with no service workers | Seconds | If Incognito shows the new version, a service worker or cache is the culprit |
| DevTools Application tab | Registered service workers and cache storage | Seconds | Directly confirms whether a stale service worker is registered and active |
This is the exact moment it hit me that the entire issue was not my code or GitHub Pages. It was a cached worker from a build that did not exist anymore.
How to confirm this is your issue too
Here are the fastest ways to check if you have the same problem.
1. Hit your site with curl
Run this in your terminal:
curl -sL https://your-site.github.io | head
curl fetches the raw HTML from the server without any browser caching or service worker interference (curl documentation). If the HTML here is newer than what your browser is showing, your browser is being lied to.
2. Open your site in Incognito
If Incognito shows the new version but your normal browser does not, that is almost always a service worker or cache problem.
3. Check DevTools
Look for an active service worker. If you see one and you did not explicitly write it, that is probably the issue.
Why this problem happens to many developers
Service workers are extremely powerful. They allow offline support, caching, background syncing, and more (MDN: Service Worker API). But the downside is that they aggressively hold onto cached files. This is on purpose. They are designed to keep apps working even if the network dies.
The problem is that GitHub Pages does not know anything about your service worker. So even after a fresh deploy, the browser can still keep serving old files because the worker is literally intercepting every request. GitHub Pages is a static hosting service (GitHub Pages documentation) and has no way to invalidate browser-side service worker caches.
This is why so many people complain that their GitHub Pages site does not update after a deploy. The deploy is fine. The browser is not.
Why this deserves a blog post
Because it is one of the most confusing and frustrating bugs you can hit as a newer developer. You can spend hours thinking your deploy is wrong, your build is wrong, your repo is wrong, or GitHub Pages is broken. But the truth is that your browser is simply loading an old cached version from a script you forgot about, or did not know existed.
Service workers can hold a website hostage if you are not careful.
What I would do differently now
Since hitting this issue, here is what I changed in my workflow:
- I never generate a service worker unless I know why I need it. Service workers should be added deliberately, not auto-generated (MDN: Service Worker Lifecycle).
- I always check DevTools when a deploy looks stuck.
- I test live sites in Incognito to avoid cache issues.
- I keep build processes simple until I fully understand what is happening.
- I clean out all AI-generated files before deploying anything.
- I add a cache-busting version query string (e.g.
?v=2) to script tags when testing updates. - I verify deploys with
curlbefore trusting the browser view.
Caching bugs like this are sneaky and they waste time, but once you see how they work, they are easy to avoid.
Final takeaway
If your website refuses to update even though your deploy is clean, it might be a stale service worker. You can confirm this by comparing the server output with curl and checking the Application tab in DevTools. Unregistering the worker fixes the issue instantly.
This bug taught me more about how the browser actually works than almost anything else I have built so far.
Keep reading
- Gatsby Nav Bar Haunted: Fixing a Stuck Dropdown
- Interactive Portfolio Basics: Keep It Fun and Still Clear
- GitHub Pages Deployments That Stop Being Confusing
References
- MDN: Service Worker API — Official documentation on the Service Worker API.
- MDN: Using Service Workers — Guide to registering and using service workers.
- MDN: Service Worker Lifecycle — The registration, installation, activation, and termination lifecycle.
- Chrome DevTools documentation — Official guide to Chrome DevTools including the Application tab.
- GitHub Pages documentation — Official GitHub Pages hosting guide.
- GitHub Actions documentation — Official documentation for CI/CD workflows on GitHub.
- curl documentation — Manual for the curl command-line tool.
