Service Workers 101: How Your Browser Becomes a Programmable Proxy 🌐

March 5, 20266 min read
service workerPWAofflinecachingweb workersprogressive web appsbackground sync

From Web Workers to Service Workers 🔄

Now that web workers make sense, service workers are the natural next step. The key distinction Kyle made early on: at some point during development, they actually considered calling it a network worker which would have been more accurate to what it actually does.

A service worker sits between your web application and the rest of the web. Every single outbound request your page makes — HTML, CSS, JavaScript files, images, favicons, AJAX calls — all of it funnels through the service worker first.

Think of it as programming your own proxy, except instead of sitting on a server, it sits right inside the browser.


How It Intercepts Requests 🚦

The service worker doesn't have to do anything with requests. If you don't listen for them, they pass through untouched zero interference with your existing traffic.

But the moment you add a fetch handler to your service worker, the rules change completely:

  1. Every outbound request now routes through your fetch handler
  2. It's now entirely on you to make sure those requests get handled correctly
  3. You essentially become the proxy you receive the request, make it on behalf of the page, and pass the response back

There's no "I don't care about this one, let it through" option. Once you're listening, you're responsible for all of it.


The CORS Wrinkle ⚠️

Here's a gotcha worth knowing before you start intercepting requests.

Normally, if your page has an <img> tag loading from a CDN, that works fine — browsers handle cross-origin images without any special headers required. But once that same request routes through your service worker and your service worker tries to make an AJAX call to that CDN:

The CDN now needs to publish CORS headers — or the request fails.

Two ways around it:

  1. Opaque requests — forward the request without inspecting the response. It goes through the service worker machinery and lands back on the page, but you never see the response content. Fine if you don't need to cache or process it.
  2. CORS-enabled responses — required if you want to cache the response or do anything meaningful with it in the service worker.

The Cache API: The Primary Use Case 💾

This is where the real power lives. At the same time service workers were introduced, the web platform also shipped the Cache API — a programmatic, developer-controlled caching layer.

Unlike the browser's built-in caching (which is complex, opaque, and not fully in your control), the Cache API gives you:

  1. Complete control over what goes in and out of the cache
  2. Access to the full URL of each cached resource
  3. The ability to create your own synthetic responses and stuff them into the cache
  4. The ability to retrieve cached responses and serve them back to the page

This is the combination that makes offline experiences possible — intercept the request via the fetch handler, check the Cache API, serve from cache if available, fall back to the network if not. Or any variation of that logic you want to write.

The service worker is the interceptor. The Cache API is the storage. Together they give you full control over your app's network behaviour.


Use Case Brainstorming 🧠

Caching is the obvious starting point but it's just scratching the surface. Kyle opened this up to the room: "If you could code the browser itself and make it do whatever you want — what would you build?"


Use Case 1: Offline Capability ✈️

The most immediate use case — and the one the course focuses on most. Store your HTML, CSS, and key resources in the Cache API so users can keep using your site even without a connection.

The Google Docs example Kyle used makes it concrete: editing a spreadsheet while completely offline, coming back online, and having everything automatically saved. That's the dream and service workers + the Cache API make it achievable on the web.


Use Case 2: Background Sync 🔄

A student mentioned a real-world scenario: field workers in Sierra Leone collecting data in villages with no internet, needing it to sync when they get back to connectivity.

This led to an important clarification about Background Sync — an API that lets the service worker say:

"I have a task that couldn't complete right now. Try again when the connection comes back, even if the tab is closed."

A few honest caveats Kyle gave:

  1. Background Sync is a spec, partially implemented in one browser not a cross-browser reality yet
  2. It may never be, due to abuse and privacy concerns
  3. A lesser but reliable version: persist data locally (IndexDB, localStorage) so it's there when the user returns and can be synced manually

The Geolocation Limitation 📍

A student asked about tracking user location in the background for an emergency check-in app. Kyle gave a thorough and honest answer:

  • Service workers cannot access geolocation — only the web page can, and only with explicit user permission
  • Background location tracking doesn't exist on the web — native apps have limited capability for this, and the web layer has none, likely for good reason
  • Geofencing was proposed (notify when a user enters/leaves a geographic area) — also didn't ship due to privacy concerns

The workaround if you truly need it: a companion native app that handles background location and relays data to the web app. Not ideal, but it's the reality.

"There are reasons why you might want to keep somebody's location in the background — and that's just not possible right now."


Use Case 3: Transparent URL Rewriting 🔀

Your page requests an image from CDN A. Your service worker detects that CDN A is down (because a previous request failed), and silently rewrites the request to CDN B instead.

The page never knows. No HTML changes needed. The service worker is acting as a smart, in-browser load balancer.


Use Case 4: Synthetic Responses 🤖

If the user is offline and the page makes an AJAX call expecting JSON data, the service worker can fabricate a response from its own code and send it back — the page receives it exactly as if it came from the server.

Combined with cached HTML and assets, this means the user can be clicking around and interacting with what feels like a live site, even with zero connectivity.


Use Case 5: Programmatic Prefetching 🚀

When a user requests one resource, the service worker can predict what they'll need next and fetch those resources proactively in the background — before the page even asks for them.

The page doesn't know this is happening. It just experiences everything loading faster.


Use Case 6: Dependency Injection 💉

Taking prefetching one step further — when the page requests one image, the service worker can:

  1. Respond with the requested image
  2. Send a message to the page telling it to request three more related resources

The page asked for one thing and effectively got four. The service worker is orchestrating resource loading in ways the page itself can't.


Bonus: Cache API Is Also Available on the Page 💡

The Cache API isn't only available inside a service worker — you can access it directly from your web page too. You won't have the request interception magic, but you can programmatically check what's already cached before making an outbound request. That's something you can never do with the browser's built-in cache.