DEV Community

Nick Romano
Nick Romano

Posted on

How to Scope Your Free Range PWA Service Workers

Progressive Web Apps are quickly becoming an engaging method of writing online applications. In order to use them properly, however, a developer needs to satisfy certain criteria in a web manifest to have the browser install the app on a user's device through "Add to Home Screen".

These criteria include adding to a web manifest:

  • short_name / name
  • icons
  • start_url
  • display

There's a few catches, however. This article will focus on a particular one: start_url and proper service worker scope when your files aren't sitting pretty in the same directory. I will assume you are deep enough in the rabbit hole of developing a PWA that concepts like HTTPS, registering service workers, app shells, and caching will be familiar to you.

What is scope? 🔭

While developing a PWA, you may have seen the warning in Chrome's DevTools under Application/Manifest:

Site cannot be installed: no matching service worker detected. You may need to reload the page, or check that the service worker for the current page also controls the start URL from the manifest

If you know your service worker is being served to the client, this error is likely due to an issue with your scope.

According to MDN,

The scope member [of a web manifest] is a string that defines the navigation scope of this web application's application context. It restricts what web pages can be viewed while the manifest is applied.

So why doesn't your service worker match?

Default scope

By default, the scope member will point to whichever directory the web manifest is located. If your manifest is at /app/manifest.json, your scope is /app/ unless you declare the scope member in your manifest.

Explicit scope

If you don't explicitly declare your scope to include the document at your start_url and the scope of your registered service worker, you are not meeting the "Add to Home Screen" criteria for PWAs, and the browser you're working with will probably annoy you about it. If your manifest is at /app/manifest.json, but your service worker is at /dist/service-worker.js, the scopes are /app/ and /dist/ respectively, and /dist/ is out of the scope of /app/.

Why is scope even relevant?

Since PWAs are really just special browser windows on the device, they need specific instructions as to which paths exist within your app and which paths are outside the intended function of the app. In real terms, pages outside the scope will display a browser bar instead of whatever display setting you have set.

Getting back in scope 👀

Let's say you're like me and use a tool like webpack to bundle your assets into a /dist/ directory, but the path to your app is /app which points to /app/index.html. This is the same situation as above. We don't want to change the code structure just to satisfy the browser's demands, so what do we do?

One option is to use a URL rewrite tool such as those in IIS to point all asset routes that would match /app/{file} to /dist/{file}. This can work as the browser will see all the assets under /app/ and stop complaining, but in my case it caused some unintended side effects with other apps that use the /dist/ directory.

Another option is to put the manifest in the root of your website as it would then include all files in the site under the same "scope", but this could cause issues especially when the site itself isn't entirely meant to be a PWA.

This helpful StackOverflow answer leads to the best answer, and the one recommended by the w3c specifications. We should add a Service-Worker-Allowed header to the service worker file's HTTP response to let the browser know the server is allowing this particular service worker script to use any scope it wants. In my case I used Service-Worker-Allowed: /.

Importantly, we also need to explicitly state our scope even with the header set from the server. For good measure, our example manifest should include

  // ... other members
  "start_url": "/app",
  "scope": "/app"

and we should use the scope parameter when we register the service worker:

navigator.serviceWorker.register('/dist/app/sw.js',{ scope: "/app"});

We now have the ability to install the PWA with any route we'd like, as long as start_url, scope, and the service worker all agree on what that scope should be.

Top comments (0)