Ressourcen in JavaScript-Frameworks einbinden

Largest Contentful Paint im gesamten JavaScript-Ökosystem verbessern

Im Rahmen des Projekts Aurora arbeitet Google mit beliebten Web-Frameworks zusammen, um sicherzustellen, dass sie gemäß den Core Web Vitals gut funktionieren. In Angular und Next.js ist das Inline-Einbinden von Schriftarten bereits möglich. Das wird im ersten Teil dieses Artikels beschrieben. Die zweite Optimierung, die wir behandeln, ist das Inlining von kritischem CSS. Diese Funktion ist jetzt standardmäßig in der Angular CLI aktiviert und wird derzeit in Nuxt.js implementiert.

Inline-Schriftarten

Nach der Analyse von Hunderten von Anwendungen stellte das Aurora-Team fest, dass Entwickler häufig Schriftarten in ihre Anwendungen einbinden, indem sie im <head>-Element von index.html darauf verweisen. Hier ein Beispiel dafür, wie das aussehen würde, wenn Sie Material Icons einfügen:

<!doctype html>
<html lang="en">
<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</html>

Dieses Muster ist zwar völlig gültig und funktionsfähig, blockiert aber das Rendern der Anwendung und führt zu einer zusätzlichen Anfrage. Um besser zu verstehen, was passiert, sehen Sie sich den Quellcode des Stylesheets an, auf das im HTML-Code oben verwiesen wird:

/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/font.woff2) format('woff2');
}

.material-icons {
  /*...*/
}

Beachten Sie, dass in der Definition von font-face auf eine externe Datei verwiesen wird, die auf fonts.gstatic.com gehostet wird. Beim Laden der Anwendung muss der Browser zuerst das ursprüngliche Stylesheet herunterladen, auf das im Head verwiesen wird.

Ein Bild, das zeigt, wie die Website eine Anfrage an den Server senden und das externe Stylesheet herunterladen muss
Zuerst wird das Schriftart-Stylesheet geladen.

Als Nächstes lädt der Browser die Datei woff2 herunter. Danach kann er die Anwendung rendern.

Ein Bild, das die beiden Anfragen zeigt, eine für das Schriftart-Stylesheet und die zweite für die Schriftartdatei.
Als Nächstes wird eine Anfrage zum Laden der Schriftart gesendet.

Eine Optimierungsmöglichkeit besteht darin, das ursprüngliche Stylesheet zur Build-Zeit herunterzuladen und in index.html einzufügen. Dadurch wird ein vollständiger Roundtrip zum CDN zur Laufzeit übersprungen, was die Blockierungszeit verkürzt.

Beim Erstellen der Anwendung wird eine Anfrage an das CDN gesendet, das das Stylesheet abruft und in die HTML-Datei einfügt. Der Domain wird ein <link rel=preconnect> hinzugefügt. Wenn wir diese Technik anwenden, erhalten wir das folgende Ergebnis:

<!doctype html>
<html lang="en">
<head>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin >
  <style type="text/css">
  @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/font.woff2) format('woff2');}.material-icons{/*...*/}</style>
  ...
</html>

Font-Inlining ist jetzt in Next.js und Angular verfügbar

Wenn Framework-Entwickler die Optimierung in den zugrunde liegenden Tools implementieren, wird es für bestehende und neue Anwendungen einfacher, sie zu aktivieren, was zu einer Verbesserung des gesamten Ökosystems führt.

Diese Verbesserung ist ab Next.js v10.2 und Angular v11 standardmäßig aktiviert. Beide unterstützen das Inline-Einbinden von Google- und Adobe-Schriftarten. Angular plant, die letztere in Version 12.2 einzuführen.

Die Implementierung von Font-Inlining in Next.js finden Sie auf GitHub. In diesem Video wird diese Optimierung im Kontext von Angular erläutert.

Kritisches CSS inline einfügen

Eine weitere Verbesserung besteht darin, die Messwerte First Contentful Paint (FCP) und Largest Contentful Paint (LCP) durch Inlining von kritischem CSS zu optimieren. Das kritische CSS einer Seite umfasst alle Styles, die beim ersten Rendern verwendet werden. Weitere Informationen zu diesem Thema finden Sie unter Nicht kritische CSS-Dateien verzögert laden.

Wir haben festgestellt, dass viele Anwendungen Stile synchron laden, was das Rendern der Anwendung blockiert. Eine schnelle Möglichkeit zur Behebung ist das asynchrone Laden der Stile. Anstatt die Skripts mit media="all" zu laden, legen Sie den Wert des Attributs media auf print fest und ersetzen Sie den Attributwert nach Abschluss des Ladevorgangs durch all:

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">

Diese Vorgehensweise kann jedoch dazu führen, dass nicht formatierte Inhalte flackern.

Die Seite flackert, während die Formatierungen geladen werden.

Im Video oben sehen Sie das Rendern einer Seite, deren Stile asynchron geladen werden. Das Flackern tritt auf, weil der Browser zuerst die Stile herunterlädt und dann den nachfolgenden HTML-Code rendert. Sobald der Browser die Stile heruntergeladen hat, wird das onload-Ereignis des Link-Elements ausgelöst, das media-Attribut wird auf all aktualisiert und die Stile werden auf das DOM angewendet.

In der Zeit zwischen dem Rendern des HTML-Codes und dem Anwenden der Formatierungen ist die Seite teilweise nicht formatiert. Wenn der Browser die Stile verwendet, kommt es zu Flimmern, was die Nutzerfreundlichkeit beeinträchtigt und zu Regressionen bei der Cumulative Layout Shift (CLS) führt.

Durch Inline-Einbettung von wichtigem CSS in Kombination mit dem asynchronen Laden von Stilen kann das Ladeverhalten verbessert werden. Mit dem Tool critters wird ermittelt, welche Stile auf der Seite verwendet werden. Dazu werden die Selektoren in einem Stylesheet mit dem HTML-Code abgeglichen. Wenn eine Übereinstimmung gefunden wird, werden die entsprechenden Stile als Teil des kritischen CSS betrachtet und inline eingefügt.

Beispiel:

Don'ts
<head>
   <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>
/* styles.css */
section button.primary {
  /* ... */
}
.list {
  /* ... */
}

Beispiel vor dem Inlining.

Im Beispiel oben liest und parst der Critter den Inhalt von styles.css. Anschließend werden die beiden Selektoren mit dem HTML abgeglichen und es wird festgestellt, dass wir section button.primary verwenden. Schließlich werden die entsprechenden Stile von Critters inline in das <head> der Seite eingefügt. Das Ergebnis:

Do
<head>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  <style>
  section button.primary {
    /* ... */
  }
  </style>
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>

Beispiel nach dem Inlining.

Nachdem Sie das kritische CSS in den HTML-Code eingefügt haben, sollte das Flimmern der Seite nicht mehr auftreten:

Die Seite wird nach dem Inline-Einbinden von CSS geladen.

Das Inline-Einbinden von kritischem CSS ist jetzt in Angular verfügbar und in Version 12 standardmäßig aktiviert. Wenn Sie Version 11 verwenden, aktivieren Sie die Funktion, indem Sie das Attribut inlineCritical in angular.json auf true setzen. Wenn Sie diese Funktion in Next.js aktivieren möchten, fügen Sie experimental: { optimizeCss: true } zu Ihrer next.config.js hinzu.

Zusammenfassung

In diesem Beitrag haben wir einige Aspekte der Zusammenarbeit zwischen Chrome und Web-Frameworks angesprochen. Wenn Sie ein Framework entwickelt haben und einige der von uns behobenen Probleme in Ihrer Technologie wiedererkennen, hoffen wir, dass unsere Erkenntnisse Sie dazu anregen, ähnliche Leistungsoptimierungen vorzunehmen.

Weitere Informationen zu den Verbesserungen Eine umfassende Liste der Optimierungsarbeiten, die wir für Core Web Vitals durchgeführt haben, finden Sie im Beitrag Introducing Aurora.