Handling redirects with a Service Worker

When I wrote about implementing my first Service Worker, I finished with this plea:

And remember, please share your code and your gotchas: it’s early days for Service Workers so every implementation counts.

Well, I ran into a gotcha that was really frustrating but thanks to the generosity of others, I was able to sort it out.

It was all because of an issue in Chrome. Here’s the problem…

Let’s say you’ve got a Service Worker running that takes care of any requests to your site. Now on that site, you’ve got a URL that receives POST data, does something with it, and then redirects to another URL. That’s a fairly common situation—it’s how I handle webmentions here on adactio.com, and it’s how I handle most add/edit/delete actions over on The Session to help prevent duplicate form submissions.

Anyway, it turns out that Chrome’s Service Worker implementation would get confused by that. Instead of redirecting, it showed the offline page instead. The fetch wasn’t resolving.

I described the situation to Jake, but rather than just try and explain it in 140 characters, I built a test case.

There’s a Chromium issue filed on this, and it will get fixed, but it in the meantime, it was really bugging me recently when I was rolling out a new feature on The Session. Matthew pointed out that the Chromium bug report also contained a workaround that he’s been using on traintimes.org.uk. Adrian also posted his expanded workaround in there too. That turned out to be exactly what I needed.

I think the problem is that the redirect means that a body is included in the GET request, which is what’s throwing the Service Worker. So I need to create a duplicate request without the body:

request = new Request(url, {
    method: 'GET',
    headers: request.headers,
    mode: request.mode == 'navigate' ? 'cors' : request.mode,
    credentials: request.credentials,
    redirect: request.redirect
});

So here’s what I had in my Service Worker before:

// For HTML requests, try the network first, fall back to the cache, finally the offline page
if (request.headers.get('Accept').indexOf('text/html') !== -1) {
    event.respondWith(
        fetch(request)
            .then( response => {
                // NETWORK
                // Stash a copy of this page in the pages cache
                let copy = response.clone();
                stashInCache(pagesCacheName, request, copy);
                return response;
            })
            .catch( () => {
                // CACHE or FALLBACK
                return caches.match(request)
                    .then( response => response || caches.match('/offline') );
                })
        );
    return;
}

And here’s what I have now:

// For HTML requests, try the network first, fall back to the cache, finally the offline page
if (request.headers.get('Accept').indexOf('text/html') !== -1) {
    request = new Request(url, {
        method: 'GET',
        headers: request.headers,
        mode: request.mode == 'navigate' ? 'cors' : request.mode,
        credentials: request.credentials,
        redirect: request.redirect
    });
    event.respondWith(
        fetch(request)
            .then( response => {
                // NETWORK
                // Stash a copy of this page in the pages cache
                let copy = response.clone();
                stashInCache(pagesCacheName, request, copy);
                return response;
            })
            .catch( () => {
                // CACHE or FALLBACK
                return caches.match(request)
                    .then( response => response || caches.match('/offline') );
                })
        );
    return;
}

Now the test case is working just fine in Chrome.

On the off-chance that someone out there is struggling with the same issue, I hope that this is useful.

Share what you learn.

Have you published a response to this? :

Responses

Aaron T. Grogg

Pure CSS tribute to Mr. Dylan… Thank you, Mr. Keith… :-)

Nice walk-through of JS‘s classList from . Support is certainly looking good, with only IE10, 11 and Mobile partially supporting it.

continues the theme with these 5 Ways that CSS and JavaScript Interact That You May Not Know About.

Was wondering how long this would take:

Pressure.js… is a JavaScript library that makes dealing with Apple’s Force Touch and 3D Touch simple.

Great collection of Service Worker resources & notes from (and not just because he mentions me… :-P ).

Please especially take note of Microsoft Edge’s intent to include Service Worker. And you can help convince them! :-)

Okay, one more from Jeremy, as he has one more note on Handling redirects with a Service Worker

And as long as we’re talking about cutting-edge JS, here is an entire battery of articles on getting started, being productive, and moving beyond, with React…

  1. From SitePoint, Building a React Universal Blog App: A Step-by-Step Guide, by , starts from the very beginning (creating the directory), provides the starting package.json file, and marches right through code sample after code sample, to get you completely set-up and running!
  2. From CSS Tricks, I Learned How to be Productive in React in a Week and You Can, Too, by , offers a fresh set of eyes, debunks a few myths about React, and also walks through code sample after code sample to demonstrate how to get up and running!
  3. Then shares 9 things every React.js beginner should know; I always feel like I should read these kinds of lists before I get started with a tech, but then it often makes no sense until I start digging in… You decide your approach.
  4. Next, starts a series on Performance Engineering with React. Always good to know performance DOs from the get-go, I feel.
  5. From Egghead.io, hopefully these React and Redux Cheatsheets will come in handy during early navigation!
  6. And finally, comes MERN, which “is a scaffolding tool which makes it easy to build isomorphic apps using Mongo, Express, React and NodeJS.” Pretty sweet!!

Whew!

Nice series about the Web Animations API. The entire series is complete, so you can binge-read start to finish as soon as you finish reading this! ;-)

is so cool… She wrote an article simply titled The `background-clip` Property and its Use Cases, which indeed begins by explaining background-clip, then showing a few very primitive explanation demos. All very nice. Then she proceeds to just slam us in the face with increasingly awesome examples by adding CSS filters and creating skeuomorphic form controls… Amazing…

And finally, reader shares his article How to Speed Up Your WordPress Blog & Make it Insanely Fast. I think most of these are well-covered, but it cannot hurt to reiterate something so important!

Happy reading, Atg