How to Scope Your Free Range PWA Service Workers 
Tags: pwa, todayilearned, webdev, javascript
Published: 2019-12-12 17:03:09

[Progressive Web Apps](https://developers.google.com/web/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](https://developers.google.com/web/fundamentals/web-app-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? :telescope:
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](https://developer.mozilla.org/en-US/docs/Web/Manifest/scope), 
> 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/`. 

![](https://media.giphy.com/media/l396InTOf9zsp2EO4/giphy.gif)

## 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 :eyes:
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](https://stackoverflow.com/a/48068714/8958801) leads to the best answer, and the one recommended by the [w3c specifications](https://w3c.github.io/ServiceWorker/#service-worker-script-response). 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
```js
{
  // ... other members
  "start_url": "/app",
  "scope": "/app"
}
```
and we should use the `scope` parameter when we register the service worker:
```js
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.