A service worker can intercept network requests for a page. It may respond to the browser with cached content, content from the network or content generated in the service worker.
workbox-routing
is a module which makes it easy to "route" these requests to
different functions that provide responses.
How Routing is Performed
When a network request causes a service worker fetch event, workbox-routing
will attempt to respond to the request using the supplied routes and handlers.
The main things to note from the above are:
The method of a request is important. By default, Routes are registered for
GET
requests. If you wish to intercept other types of requests, you'll need to specify the method.The order of the Route registration is important. If multiple Routes are registered that could handle a request, the Route that is registered first will be used to respond to the request.
There are a few ways to register a route: you can use callbacks, regular expressions or Route instances.
Matching and Handling in Routes
A "route" in workbox is nothing more than two functions: a "matching" function to determine if the route should match a request and a "handling" function, which should handle the request and respond with a response.
Workbox comes with some helpers that'll perform the matching and handling for you, but if you ever find yourself wanting different behavior, writing a custom match and handler function is the best option.
A
match callback function
is passed a
ExtendableEvent
,
Request
, and a
URL
object you can
match by returning a truthy value. For a simple example, you could match against
a specific URL like so:
const matchCb = ({url, request, event}) => {
return url.pathname === '/special/url';
};
Most use cases can be covered by examining / testing either the url
or the
request
.
A
handler callback function
will be given the same
ExtendableEvent
,
Request
, and
URL
object along with
a params
value, which is the value returned by the "match" function.
const handlerCb = async ({url, request, event, params}) => {
const response = await fetch(request);
const responseBody = await response.text();
return new Response(`${responseBody} <!-- Look Ma. Added Content. -->`, {
headers: response.headers,
});
};
Your handler must return a promise that resolves to a Response
. In this
example, we're using
async
and await
.
Under the hood, the return Response
value will be wrapped in a promise.
You can register these callbacks like so:
import {registerRoute} from 'workbox-routing';
registerRoute(matchCb, handlerCb);
The only limitation is that the "match" callback must synchronously return a truthy
value, you can't perform any asynchronous work. The reason for this is that
the Router
must synchronously respond to the fetch event or allow falling
through to other fetch events.
Normally the "handler" callback would use one of the strategies provided by workbox-strategies like so:
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
registerRoute(matchCb, new StaleWhileRevalidate());
In this page, we'll focus on workbox-routing
but you can
learn more about these strategies on workbox-strategies.
How to Register a Regular Expression Route
A common practice is to use a regular expression instead of a "match" callback. Workbox makes this easy to implement like so:
import {registerRoute} from 'workbox-routing';
registerRoute(new RegExp('/styles/.*\\.css'), handlerCb);
For requests from the same origin, this regular expression will match as long as the request's URL matches the regular expression.
- https://example.com/styles/main.css
- https://example.com/styles/nested/file.css
- https://example.com/nested/styles/directory.css
However, for cross-origin requests, regular expressions
must match the beginning of the URL. The reason for this is that it's
unlikely that with a regular expression new RegExp('/styles/.*\\.css')
you intended to match third-party CSS files.
- https://cdn.third-party-site.com/styles/main.css
- https://cdn.third-party-site.com/styles/nested/file.css
- https://cdn.third-party-site.com/nested/styles/directory.css
If you did want this behaviour, you just need to ensure that the regular
expression matches the beginning of the URL. If we wanted to match the
requests for https://cdn.third-party-site.com
we could use the regular
expression new RegExp('https://cdn\\.third-party-site\\.com.*/styles/.*\\.css')
.
- https://cdn.third-party-site.com/styles/main.css
- https://cdn.third-party-site.com/styles/nested/file.css
- https://cdn.third-party-site.com/nested/styles/directory.css
If you wanted to match both local and third parties you can use a wildcard at the start of your regular expression, but this should be done with caution to ensure it doesn't cause unexpected behaviors in your web app.
How to Register a Navigation Route
If your site is a single page app, you can use a
NavigationRoute
to
return a specific response for all
navigation requests.
import {createHandlerBoundToURL} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';
// This assumes /app-shell.html has been precached.
const handler = createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler);
registerRoute(navigationRoute);
Whenever a user goes to your site in the browser, the request for the page will
be a navigation request and it will be served the cached page /app-shell.html
.
(Note: You should have the page cached via workbox-precaching
or through your
own installation step.)
By default, this will respond to all navigation requests. If you want to
restrict it to respond to a subset of URLs, you can use the allowlist
and denylist
options to restrict which pages will match this route.
import {createHandlerBoundToURL} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';
// This assumes /app-shell.html has been precached.
const handler = createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler, {
allowlist: [new RegExp('/blog/')],
denylist: [new RegExp('/blog/restricted/')],
});
registerRoute(navigationRoute);
The only thing to note is that the denylist
will win if a URL is in both
the allowlist
and denylist
.
Set a Default Handler
If you want to supply a "handler" for requests that don't match a route, you can set a default handler.
import {setDefaultHandler} from 'workbox-routing';
setDefaultHandler(({url, event, params}) => {
// ...
});
Set a Catch Handler
In the case of any of your routes throwing an error, you can capture and degrade gracefully by setting a catch handler.
import {setCatchHandler} from 'workbox-routing';
setCatchHandler(({url, event, params}) => {
...
});
Defining a Route for Non-GET Requests
All routes by default are assumed to be for GET
requests.
If you would like to route other requests, like a POST
request, you need
to define the method when registering the route, like so:
import {registerRoute} from 'workbox-routing';
registerRoute(matchCb, handlerCb, 'POST');
registerRoute(new RegExp('/api/.*\\.json'), handlerCb, 'POST');
Router Logging
You should be able to determine the flow of a request using the logs from
workbox-routing
which will highlight which URLs are being processed
through Workbox.
If you need more verbose information, you can set the log level to debug
to
view logs on requests not handled by the Router. See our
debugging guide for more info on
setting the log level.
Advanced Usage
If you want to have more control over when the Workbox Router is given
requests, you can create your own
Router
instance and call
it's handleRequest()
method whenever you want to use the router to respond to a request.
import {Router} from 'workbox-routing';
const router = new Router();
self.addEventListener('fetch', event => {
const {request} = event;
const responsePromise = router.handleRequest({
event,
request,
});
if (responsePromise) {
// Router found a route to handle the request.
event.respondWith(responsePromise);
} else {
// No route was found to handle the request.
}
});
When using the Router
directly, you will also need to use the Route
class,
or any of the extending classes to register routes.
import {Route, RegExpRoute, NavigationRoute, Router} from 'workbox-routing';
const router = new Router();
router.registerRoute(new Route(matchCb, handlerCb));
router.registerRoute(new RegExpRoute(new RegExp(...), handlerCb));
router.registerRoute(new NavigationRoute(handlerCb));
Types
NavigationRoute
NavigationRoute makes it easy to create a
workbox-routing.Route
that matches for browser
[navigation requests]https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests
.
It will only match incoming Requests whose
https://fetch.spec.whatwg.org/#concept-request-mode|mode
is set to navigate
.
You can optionally only apply this route to a subset of navigation requests
by using one or both of the denylist
and allowlist
parameters.
Properties
-
void
If both
denylist
andallowlist
are provided, thedenylist
will take precedence and the request will not match this route.The regular expressions in
allowlist
anddenylist
are matched against the concatenated [pathname
]https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname
and [search
]https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search
portions of the requested URL.Note: These RegExps may be evaluated against every destination URL during a navigation. Avoid using complex RegExps, or else your users may see delays when navigating your site.
The
constructor
function looks like:(handler: RouteHandler, options?: NavigationRouteMatchOptions) => {...}
-
A callback function that returns a Promise resulting in a Response.
-
NavigationRouteMatchOptions optional
-
-
RouteHandlerObject optional
-
HTTPMethod
-
void
The
setCatchHandler
function looks like:(handler: RouteHandler) => {...}
-
A callback function that returns a Promise resolving to a Response
-
NavigationRouteMatchOptions
Properties
-
RegExp[] optional
-
RegExp[] optional
RegExpRoute
RegExpRoute makes it easy to create a regular expression based
workbox-routing.Route
.
For same-origin requests the RegExp only needs to match part of the URL. For requests against third-party servers, you must define a RegExp that matches the start of the URL.
Properties
-
constructor
void
If the regular expression contains [capture groups]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references
, the captured values will be passed to theworkbox-routing~handlerCallback
params
argument.The
constructor
function looks like:(regExp: RegExp, handler: RouteHandler, method?: HTTPMethod) => {...}
-
regExp
RegExp
The regular expression to match against URLs.
-
handler
A callback function that returns a Promise resulting in a Response.
-
method
HTTPMethod optional
-
returns
-
-
catchHandler
RouteHandlerObject optional
-
handler
-
match
-
method
HTTPMethod
-
setCatchHandler
void
The
setCatchHandler
function looks like:(handler: RouteHandler) => {...}
-
handler
A callback function that returns a Promise resolving to a Response
-
Route
A Route
consists of a pair of callback functions, "match" and "handler".
The "match" callback determine if a route should be used to "handle" a
request by returning a non-falsy value if it can. The "handler" callback
is called when there is a match and should return a Promise that resolves
to a Response
.
Properties
-
constructor
void
Constructor for Route class.
The
constructor
function looks like:(match: RouteMatchCallback, handler: RouteHandler, method?: HTTPMethod) => {...}
-
match
A callback function that determines whether the route matches a given
fetch
event by returning a non-falsy value. -
handler
A callback function that returns a Promise resolving to a Response.
-
method
HTTPMethod optional
-
returns
-
-
catchHandler
RouteHandlerObject optional
-
handler
-
match
-
method
HTTPMethod
-
setCatchHandler
void
The
setCatchHandler
function looks like:(handler: RouteHandler) => {...}
-
handler
A callback function that returns a Promise resolving to a Response
-
Router
The Router can be used to process a FetchEvent
using one or more
workbox-routing.Route
, responding with a Response
if
a matching route exists.
If no route matches a given a request, the Router will use a "default" handler if one is defined.
Should the matching Route throw an error, the Router will use a "catch" handler if one is defined to gracefully deal with issues and respond with a Request.
If a request matches multiple routes, the earliest registered route will be used to respond to the request.
Properties
-
constructor
void
Initializes a new Router.
The
constructor
function looks like:() => {...}
-
returns
-
-
routes
Map<HTTPMethodRoute[]>
-
addCacheListener
void
Adds a message event listener for URLs to cache from the window. This is useful to cache resources loaded on the page prior to when the service worker started controlling it.
The format of the message data sent from the window should be as follows. Where the
urlsToCache
array may consist of URL strings or an array of URL string +requestInit
object (the same as you'd pass tofetch()
).{ type: 'CACHE_URLS', payload: { urlsToCache: [ './script1.js', './script2.js', ['./script3.js', {mode: 'no-cors'}], ], }, }
The
addCacheListener
function looks like:() => {...}
-
addFetchListener
void
Adds a fetch event listener to respond to events when a route matches the event's request.
The
addFetchListener
function looks like:() => {...}
-
findMatchingRoute
void
Checks a request and URL (and optionally an event) against the list of registered routes, and if there's a match, returns the corresponding route along with any params generated by the match.
The
findMatchingRoute
function looks like:(options: RouteMatchCallbackOptions) => {...}
-
options
-
returns
object
An object with
route
andparams
properties. They are populated if a matching route was found orundefined
otherwise.
-
-
handleRequest
void
Apply the routing rules to a FetchEvent object to get a Response from an appropriate Route's handler.
The
handleRequest
function looks like:(options: object) => {...}
-
options
object
-
event
ExtendableEvent
The event that triggered the request.
-
request
Request
The request to handle.
-
-
returns
Promise<Response>
A promise is returned if a registered route can handle the request. If there is no matching route and there's no
defaultHandler
,undefined
is returned.
-
-
registerRoute
void
Registers a route with the router.
The
registerRoute
function looks like:(route: Route) => {...}
-
route
The route to register.
-
-
setCatchHandler
void
If a Route throws an error while handling a request, this
handler
will be called and given a chance to provide a response.The
setCatchHandler
function looks like:(handler: RouteHandler) => {...}
-
handler
A callback function that returns a Promise resulting in a Response.
-
-
setDefaultHandler
void
Define a default
handler
that's called when no routes explicitly match the incoming request.Each HTTP method ('GET', 'POST', etc.) gets its own default handler.
Without a default handler, unmatched requests will go against the network as if there were no service worker present.
The
setDefaultHandler
function looks like:(handler: RouteHandler, method?: HTTPMethod) => {...}
-
handler
A callback function that returns a Promise resulting in a Response.
-
method
HTTPMethod optional
-
-
unregisterRoute
void
Unregisters a route with the router.
The
unregisterRoute
function looks like:(route: Route) => {...}
-
route
The route to unregister.
-
Methods
registerRoute()
workbox-routing.registerRoute(
capture: string | RegExp | RouteMatchCallback | Route,
handler?: RouteHandler,
method?: HTTPMethod,
)
Easily register a RegExp, string, or function with a caching strategy to a singleton Router instance.
This method will generate a Route for you if needed and
call workbox-routing.Router#registerRoute
.
Parameters
-
capture
string | RegExp | RouteMatchCallback | Route
If the capture param is a
Route
, all other arguments will be ignored. -
handler
RouteHandler optional
-
method
HTTPMethod optional
Returns
-
The generated
Route
.
setCatchHandler()
workbox-routing.setCatchHandler(
handler: RouteHandler,
)
If a Route throws an error while handling a request, this handler
will be called and given a chance to provide a response.
Parameters
-
handler
A callback function that returns a Promise resulting in a Response.
setDefaultHandler()
workbox-routing.setDefaultHandler(
handler: RouteHandler,
)
Define a default handler
that's called when no routes explicitly
match the incoming request.
Without a default handler, unmatched requests will go against the network as if there were no service worker present.
Parameters
-
handler
A callback function that returns a Promise resulting in a Response.