ServiceWorker &
Loading Performance
#serviceworkers irc.w3c.org
Shunya Shishido (sisidovski@chromium.org)
Breakout in TPAC 2023 Seville, Spain
Agenda
Background & Motivations
How ServiceWorker works
ServiceWorker is on the critical path of the navigation and subresource requests!
example.com/article/page.html
ServiceWorker
Network
Cache
The bootstrap is required if it’s not started yet.
Cost of ServiceWorker
Cost of ServiceWorker
Cost of ServiceWorker
*fallback: fetch handler is executed, but the fetch handler never respond the content. The browser ends up sending the network request with the regular network stack.
Navigation Preload
addEventListener('activate', event => {
event.waitUntil(async function() {
await self.registration.navigationPreload.enable();
}());
});
Navigation preload improve service worker startup time by making requests in parallel.
without navigation preload
with navigation preload
Navigation request
SW boot
Navigation request
SW boot
Navigation Preload
addEventListener('fetch', event => {
event.respondWith(async function() {
const response = await event.preloadResponse;
if (response) {
return response;
}
return fetch(event.request);
}());
});
Navigation request
SW boot
With navigation preload, but the SW boot may take longer time than the navigation request…
How to minimize the cost
Strategy: don’t start ServiceWorker if not needed!
example.com/article/page.html
ServiceWorker
You don’t need me because…
Skip no-op fetch handler
Explainer: https://github.com/yoshisatoyanagisawa/service-worker-skip-no-op-fetch-handler
Spec Update: https://github.com/w3c/ServiceWorker/pull/1674
An optimization to skip unnecessary fetch hander executions.
If the fetch handler is empty, navigation doesn’t involve ServiceWorker.
Still not 100% shipped yet in Chrome, but so far we see about 50-200ms LCP improvements on the sites having no-op fetch handlers.
addEventListener(‘fetch’, () => {});
BypassFetchHandlerForMainResources
https://github.com/sisidovski/service-worker-bypass-fetch-handler-for-main-resource
Static Routing API
// Go straight to the network and bypass invoking fetch handlers for all URLs that start with '/images/'.
addEventListener('install', (event) => {
event.registerRouter({
condition: {
urlPattern: {pathname: "/images/*"}
},
source: "network"
});
});
Explainer: https://github.com/WICG/service-worker-static-routing-api
Spec update: https://github.com/w3c/ServiceWorker/pull/1686
Static Routing API allows developers to register routing rules, it can offload ServiceWorker related tasks from the loading critical path.
Static Routing API
URLPattern in Static Routing API
const pattern = new URLPattern({
hostname: `{*.}?example.com`
});
// Prints `true`
console.log(pattern.test("https://example.com/foo/bar"));
// Prints `true`
console.log(pattern.test({ hostname: "cdn.example.com" }));
// Prints `false` because the hostname component does not match
console.log(pattern.test("https://cdn-example.com/foo/bar"));
The URLPattern API provides a web platform primitive for matching URLs.
https://wicg.github.io/urlpattern/
This brings an unified and flexible way to write matching rules.
Static Routing API
addEventListener('install', (event) => {
event.registerRouter({
condition: {
urlPattern: {pathname: "/article/*"}
},
source: "network"
});
});
How it works:
In the install event handler, register routing info to the data structure associated with service worker.
example.com
Static Routing API
addEventListener('install', (event) => {
event.registerRouter({
condition: {
urlPattern: {pathname: "/article/*"}
},
source: "network"
});
});
Check if the request is matched with the registered routing rules.
example.com
GET example.com/article/page.html
If matched, the fetch event is not dispatched. The request goes directly to the network.
How it works:
Static Routing API
addEventListener('install', (event) => {
event.registerRouter({
condition: {
urlPattern: {pathname: "/article/*"}
},
source: "network"
});
});
Check if the request is matched with the registered routing rules.
example.com
GET example.com/article/page.html
How it works:
Network
Cache
If not matched, just go the through regular ServiceWorker fetch handler path.
Static Routing API common use case
addEventListener('install', (event) => {
event.registerRouter({
condition: {
requestMode: "navigate"
},
source: "network"
});
});
Skip SW for navigation requests
Main resource (navigation request) goes directly to the network.
Subresources (JS, CSS, images etc) goes to the ServiceWorker
Static Routing API common use case
addEventListener('install', (event) => {
event.registerRouter({
condition: {
and: [
{urlPattern: {pathname: "/form/*"}},
{requestMethod: "post"}
],
},
source: "network"
});
});
Skip POST requests in some pages
If the request url starts from “form” and method is POST, goes directly to the network.
Otherwise goes to the ServiceWorker.
Static Routing API common use case
addEventListener('install', (event) => {
event.registerRouter([
{
condition: {requestMode: "navigate"},
source: "network"
},
{
condition: {
urlPattern: {pathname: "/images/*"}
},
source: "cache"
}
]);
});
Skip SW for navigation requests, go directly to the cache for subresources.
Main resource (navigation request) goes directly to the network.
Images goes directly to the Cache storage.
Static Routing API common use case
// Cache first for JavaScript files.
addEventListener('install', (event) => {
event.registerRouter({
condition: {
urlPattern: {pathname: "/**/*.js"}
},
source: [
{
cacheName: "javascript"
},
"network"
]
});
});
Cache First
If there is no cache hit, fallback to the the network.
Goes directly to the Cache storage.
Static Routing API common use case
// Race for all URLs.
addEventListener('install', (event) => {
event.registerRouter({
condition: {
urlPattern: {}
},
source: "race-network-and-fetch-handler"
});
});
“race-network-and-fetch-handler” is an experimental option starting the race between the network request and the ServiceWorker fetch handler.
The browser uses the result of whichever was faster.
Race between the network and fetch handlers
Implementation Status in Chrome
Origin Trial is available! from Chrome 116 to 121. Early feedback is appreciated.
Chrome implements the feature step by step.
Full set of the API shape: https://github.com/WICG/service-worker-static-routing-api/blob/main/final-form.md
Related Links
Skip service worker no-op fetch handler
ServiceWorkerBypassFetchHandlerForMainResources
Static Routing API
Ideas Open for Discussion