Sofortige Navigation

Ergänzung herkömmlicher Prefetching-Techniken mit Service Workern

Demián Renzulli
Demián Renzulli
Gilberto Cocchi
Gilberto Cocchi

Die Ausführung einer Aufgabe auf einer Website umfasst in der Regel mehrere Schritte. Wenn Sie beispielsweise ein Produkt auf einer E-Commerce-Website kaufen, suchen Sie möglicherweise nach einem Produkt, wählen einen Artikel aus der Ergebnisliste aus, fügen den Artikel dem Einkaufswagen hinzu und schließen den Vorgang ab, indem Sie zur Kasse gehen.

Technisch gesehen bedeutet das Aufrufen verschiedener Seiten, dass eine Navigationsanfrage gestellt wird. In der Regel sollten Sie keine langlebigen Cache-Control-Header verwenden, um die HTML-Antwort für eine Navigationsanfrage im Cache zu speichern. Sie sollten normalerweise über das Netzwerk mit Cache-Control: no-cache erfüllt werden, damit das HTML zusammen mit der Kette nachfolgender Netzwerkanfragen (angemessen) aktuell ist. Wenn bei jedem Aufrufen einer neuen Seite eine Anfrage an das Netzwerk gesendet werden muss, kann das leider dazu führen, dass jede Navigation langsam ist – zumindest ist sie nicht zuverlässig schnell.

Um diese Anfragen zu beschleunigen, können Sie diese Seiten und Assets vorab anfordern und für kurze Zeit im Cache speichern, bis der Nutzer auf diese Links klickt. Diese Technik wird als Prefetching bezeichnet. Sie wird in der Regel implementiert, indem <link rel="prefetch">-Tags auf Seiten hinzugefügt werden, die die vorab abzurufende Ressource angeben.

In diesem Leitfaden werden verschiedene Möglichkeiten untersucht, wie Service Worker als Ergänzung zu herkömmlichen Prefetching-Techniken verwendet werden können.

Produktionsfälle

MercadoLibre ist die größte E-Commerce-Website in Lateinamerika. Um die Navigation zu beschleunigen, werden <link rel="prefetch">-Tags in einigen Teilen des Ablaufs dynamisch eingefügt. Auf Angebotsseiten wird beispielsweise die nächste Ergebnisseite abgerufen, sobald der Nutzer zum Ende der Liste scrollt:

Screenshot der ersten und zweiten Eintragsseite von MercadoLibre mit einem Link-Prefetch-Tag, das beide Seiten verbindet.

Vorgeholte Dateien werden mit der Priorität „Niedrig“ angefordert und im HTTP-Cache oder Arbeitsspeicher-Cache gespeichert (je nachdem, ob die Ressource im Cache gespeichert werden kann oder nicht). Die Speicherdauer variiert je nach Browser. In Chrome 85 beträgt dieser Wert beispielsweise 5 Minuten. Ressourcen werden fünf Minuten lang aufbewahrt. Danach gelten die normalen Cache-Control-Regeln für die Ressource.

Durch die Verwendung von Service Worker-Caching können Sie die Lebensdauer von Prefetch-Ressourcen über das 5‑Minuten-Zeitfenster hinaus verlängern.

Das italienische Sportportal Virgilio Sport verwendet beispielsweise Service Worker, um die beliebtesten Beiträge auf der Startseite vorab abzurufen. Außerdem wird die Network Information API verwendet, um das Prefetching für Nutzer mit einer 2G-Verbindung zu vermeiden.

Logo von Virgilio Sport

In den drei Wochen des Beobachtungszeitraums konnte Virgilio Sport die Ladezeiten für die Navigation zu Artikeln um 78% und die Anzahl der Artikelimpressionen um 45% verbessern.

Screenshot der Startseite und der Artikelseiten von Virgilio Sport mit den Auswirkungen von Prefetching.

Precaching mit Workbox implementieren

Im folgenden Abschnitt wird anhand von Workbox gezeigt, wie verschiedene Caching-Techniken im Service Worker implementiert werden können, die als Ergänzung oder sogar als Ersatz für <link rel="prefetch"> dienen können, indem diese Aufgabe vollständig an den Service Worker delegiert wird.

1. Statische Seiten und Seitenunterressourcen vorab im Cache speichern

Precaching ist die Möglichkeit des Service Workers, Dateien während der Installation im Cache zu speichern.

In den folgenden Fällen wird Precaching verwendet, um ein ähnliches Ziel wie Prefetching zu erreichen: schnellere Navigation.

Statische Seiten vorab im Cache speichern

Bei Seiten, die zur Build-Zeit generiert werden (z.B. about.html, contact.html) oder bei vollständig statischen Websites können Sie die Dokumente der Website einfach der Precache-Liste hinzufügen, damit sie jedes Mal, wenn der Nutzer darauf zugreift, bereits im Cache verfügbar sind:

workbox.precaching.precacheAndRoute([
  {url: '/about.html', revision: 'abcd1234'},
  // ... other entries ...
]);

Unterressourcen von Seiten vorab im Cache speichern

Statische Assets, die in den verschiedenen Bereichen der Website verwendet werden können (z.B. JavaScript, CSS usw.), vorab zu cachen, ist eine allgemeine Best Practice und kann in Prefetching-Szenarien für einen zusätzlichen Schub sorgen.

Um die Navigation auf einer E-Commerce-Website zu beschleunigen, können Sie <link rel="prefetch">-Tags auf Listenseiten verwenden, um die Seiten mit Produktdetails für die ersten Produkte einer Listenseite vorab abzurufen. Wenn Sie die untergeordneten Ressourcen der Produktseite bereits im Cache gespeichert haben, kann die Navigation noch schneller erfolgen.

So implementieren Sie das:

  • Fügen Sie der Seite ein <link rel="prefetch">-Tag hinzu:
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • Fügen Sie die untergeordneten Seitenressourcen der Precache-Liste im Service Worker hinzu:
workbox.precaching.precacheAndRoute([
  '/styles/product-page.ac29.css',
  // ... other entries ...
]);

2. Lebensdauer von Prefetch-Ressourcen verlängern

Wie bereits erwähnt, ruft <link rel="prefetch"> Ressourcen ab und speichert sie für eine begrenzte Zeit im HTTP-Cache. Danach gelten die Cache-Control-Regeln für eine Ressource. Ab Chrome 85 beträgt dieser Wert 5 Minuten.

Mit Service Workern können Sie die Lebensdauer der vorab abgerufenen Seiten verlängern und gleichzeitig dafür sorgen, dass diese Ressourcen auch offline verfügbar sind.

Im vorherigen Beispiel könnte man das <link rel="prefetch">, das zum Prefetching einer Produktseite verwendet wird, mit einer Workbox-Laufzeit-Caching-Strategie ergänzen.

So implementieren Sie das:

  • Fügen Sie der Seite ein <link rel="prefetch">-Tag hinzu:
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • Implementieren Sie eine Laufzeit-Caching-Strategie im Service Worker für diese Arten von Anfragen:
new workbox.strategies.StaleWhileRevalidate({
  cacheName: 'document-cache',
  plugins: [
    new workbox.expiration.Plugin({
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
    }),
  ],
});

In diesem Fall haben wir uns für die Strategie „Stale-while-revalidate“ entschieden. Bei dieser Strategie können Seiten parallel sowohl aus dem Cache als auch aus dem Netzwerk angefordert werden. Die Antwort stammt aus dem Cache, sofern verfügbar, andernfalls aus dem Netzwerk. Der Cache wird bei jeder erfolgreichen Anfrage immer mit der Netzwerkantwort auf dem neuesten Stand gehalten.

3. Prefetching an den Service Worker delegieren

In den meisten Fällen ist es am besten, <link rel="prefetch"> zu verwenden. Das Tag ist ein Ressourcenhinweis, der das Prefetching so effizient wie möglich gestalten soll.

In einigen Fällen ist es jedoch besser, diese Aufgabe vollständig an den Service Worker zu delegieren. Wenn Sie beispielsweise die ersten Produkte auf einer clientseitig gerenderten Produktlistenseite vorab abrufen möchten, müssen Sie möglicherweise mehrere <link rel="prefetch">-Tags dynamisch in die Seite einfügen, basierend auf einer API-Antwort. Das kann vorübergehend Zeit im Hauptthread der Seite in Anspruch nehmen und die Implementierung erschweren.

In solchen Fällen sollten Sie eine „Strategie für die Kommunikation zwischen Seite und Service Worker“ verwenden, um die Aufgabe des Prefetching vollständig an den Service Worker zu delegieren. Diese Art der Kommunikation kann mit worker.postMessage() erreicht werden:

Ein Symbol einer Seite, die eine bidirektionale Kommunikation mit einem Service Worker herstellt.

Das Workbox Window-Paket vereinfacht diese Art der Kommunikation, indem viele Details des zugrunde liegenden Aufrufs abstrahiert werden.

Das Vorabrufen mit Workbox Window kann so implementiert werden:

  • Rufen Sie auf der Seite den Service Worker auf und übergeben Sie ihm den Nachrichtentyp und die Liste der vorab abzurufenden URLs:
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: []});
  • Implementieren Sie im Service Worker einen Message-Handler, um für jede vorzufetchende URL eine fetch()-Anfrage zu senden:
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});