Range requests in service workers: what changed

Some HTTP requests include a Range: header to ask for only part of a resource. This is how streaming audio and video players fetch media in chunks rather than downloading the whole file. A service worker sits between the browser and the network and can intercept these requests. Historically, this combination has been problematic, but recent browser updates make range requests pass through service workers cleanly in most cases.

Why range requests broke in service workers

Consider a service worker that forwards every request to the network:

self.addEventListener('fetch', (event) => {
  // The Range: header will not pass through in
  // browsers that behave incorrectly.
  event.respondWith(fetch(event.request));
});

In older browsers, passing event.request to fetch() silently stripped the Range: header. The remote server never saw the range request and returned the full resource with a 200 status. That is technically valid—servers are allowed to respond with the full body even when a Range: header is present—but it made media streaming transfer more data than needed.

A workaround was to explicitly check for the header and skip event.respondWith() when it was present, letting the browser's native networking logic handle the request:

self.addEventListener('fetch', (event) => {
  // Return without calling event.respondWith()
  // if this is a range request.
  if (event.request.headers.has('range')) {
    return;
  }

  event.respondWith(fetch(event.request));
});

Most developers didn't know this was necessary. The limitation came from browsers lagging behind an update to the Fetch specification that added range request support.

What the fix looks like

Browsers with correct behavior now preserve the Range: header when a request is passed to fetch() inside a service worker:

self.addEventListener('fetch', (event) => {
  // The Range: header will pass through in browsers
  // that behave correctly.
  event.respondWith(fetch(event.request));
});

The remote server receives the original Range: header and can respond with a 206 partial content response.

Browser support

  • Safari: recent versions have the correct behavior.
  • Chrome and Edge: correct starting with version 87.
  • Firefox: as of October 2020, the behavior has not been fixed.

If you need to support Firefox in production, you still need the explicit Range: header check and fallback to default networking. The Web Platform Tests dashboard row "Include range header in network request" is the authoritative way to confirm a given browser's behavior.

Serving range requests from the cache

Service workers also commonly serve media files from a local cache, bypassing the network. All browsers, including Firefox, allow a service worker to inspect a request's Range: header and respond with a locally cached 206 partial response.

The logic for parsing the header and returning the correct byte range from a cached full response is non-trivial. The Workbox library's workbox-range-request module implements this logic for you. Workbox's documentation includes a full recipe for serving cached audio and video with range request support.