Verbesserte Next.js- und Gatsby-Seitenladeleistung durch detailliertes Aufteilen

Eine neuere Webpack-Aufteilungsstrategie in Next.js und Gatsby minimiert Codeduplikaten, um die Leistung beim Laden von Seiten zu verbessern.

Chrome kooperiert mit Tools und Frameworks in der JavaScript-Open-Source-Umgebung. Kürzlich wurden einige Optimierungen wurde hinzugefügt, um die Ladeleistung von Next.js zu verbessern und Gatsby: In diesem Artikel wird eine verbesserte, detaillierte Aufteilungsstrategie behandelt. das jetzt standardmäßig in beiden Frameworks bereitgestellt wird.

Einführung

Wie viele Web-Frameworks verwenden Next.js und Gatsby webpack als Kernstück Bundler. Einführung von Webpack v3 CommonsChunkPlugin, damit Sie Ausgabemodule, die zwischen verschiedenen Einstiegspunkten in einem einzigen (oder wenigen) "Commons" verwendet werden Chunk (oder Blöcke). Freigegebener Code kann separat heruntergeladen und frühzeitig im Browser-Cache gespeichert werden. zu einer besseren Ladeleistung führen.

Dieses Muster wurde bei vielen Single-Page-Frameworks beliebt, die einen Einstiegspunkt und Bundle-Konfiguration so aussieht:

Gemeinsame Einstiegspunkt- und Bundle-Konfiguration

Das Konzept, den gesamten Code aus gemeinsam genutzten Modulen in einem Block zu bündeln, ist zwar praktikabel, hat aber Einschränkungen. Module, die nicht in jedem Einstiegspunkt freigegeben sind, können für Routen heruntergeladen werden, die sie nicht verwenden Dadurch wird mehr Code als nötig heruntergeladen. Beispiel: Wenn page1 geladen wird, dem Chunk common wird der Code für moduleC geladen, obwohl page1 moduleC nicht verwendet. Aus diesem und einigen anderen Gründen hat Webpack v4 das Plug-in zugunsten eines neuen 1: SplitChunksPlugin.

Verbesserte Chunking-Methode

Die Standardeinstellungen für SplitChunksPlugin eignen sich für die meisten Nutzer gut. Mehrere aufgeteilte Blöcke werden die abhängig von verschiedenen Bedingungen erstellt werden. um zu verhindern, dass doppelter Code über mehrere Routen hinweg abgerufen wird.

Viele Web-Frameworks, die dieses Plug-in verwenden, folgen jedoch immer noch einem allgemeinen Konzept. Ansatz für Chunk die Aufteilung. Next.js würde beispielsweise ein commons-Bundle generieren, das ein beliebiges Modul enthält, das werden auf mehr als 50% der Seiten und allen Framework-Abhängigkeiten (react, react-dom usw.) verwendet.

const splitChunksConfigs = {
  …
  prod: {
    chunks: 'all',
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: 'commons',
        chunks: 'all',
        minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
      },
      react: {
        name: 'commons',
        chunks: 'all',
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
      },
    },
  },

Obwohl das Einbinden von Framework-abhängigem Code in einen gemeinsamen Block bedeutet, kann dieser heruntergeladen und für jeden Einstiegspunkt, die nutzungsbasierte Heuristik der Einbeziehung allgemeiner Module, die in mehr als auf der Hälfte der Seiten nicht sehr effektiv ist. Die Änderung dieses Verhältnisses würde nur eines von zwei Ergebnissen zur Folge haben:

  • Wenn Sie das Verhältnis verringern, wird mehr unnötiger Code heruntergeladen.
  • Wenn Sie das Verhältnis erhöhen, wird mehr Code für mehrere Routen dupliziert.

Zur Lösung dieses Problems führte Next.js eine andere Konfiguration fürSplitChunksPlugin, die reduzierte unnötigen Code für jede Route.

  • Jedes ausreichend große Drittanbietermodul (größer als 160 KB) wird in ein eigenes Chunk
  • Für Framework-Abhängigkeiten (react, react-dom undframeworks so weiter)
  • Es werden so viele freigegebene Blöcke wie nötig erstellt (bis zu 25).
  • Die Mindestgröße für einen zu generierenden Block wird in 20 KB geändert

Diese detaillierte Aufteilungsstrategie bietet folgende Vorteile:

  • Die Seitenladezeiten wurden verbessert. Werden mehrere freigegebene Blöcke anstelle eines einzelnen Blöcke ausgegeben, minimiert die Menge an unnötigem (oder doppeltem) Code für einen Einstiegspunkt.
  • Verbessertes Caching während der Navigation: Große Bibliotheken und Framework-Abhängigkeiten aufteilen in separate Blöcke unterteilt, was die Wahrscheinlichkeit einer Cache-Entwertung verringert, da es bei beiden unwahrscheinlich ist, bis ein Upgrade durchgeführt wird.

Sie können die gesamte Konfiguration sehen, die Next.js in webpack-config.ts übernommen hat.

Mehr HTTP-Anfragen

SplitChunksPlugin hat die Grundlage für die detaillierte Aufteilung definiert und diesen Ansatz auf eine wie Next.js kein völlig neues Konzept war. Viele Konzepte wurden jedoch eine einzige Heuristik und „Commons“ für eine einheitliche Bündelung. Dazu gehört auch die Sorge, und mehr HTTP-Anfragen die Websiteleistung beeinträchtigen können.

Browser können nur eine begrenzte Anzahl von TCP-Verbindungen zu einem einzigen Ursprung (6 für Chrome) öffnen. Durch das Minimieren der Anzahl von Blöcken, die von einem Bundler ausgegeben werden, kann sichergestellt werden, unter diesem Grenzwert bleibt. Dies gilt jedoch nur für HTTP/1.1. Multiplexing in HTTP/2 können mehrere Anfragen parallel gestreamt werden, wobei eine Verbindung über eine einzige Ursprung. Mit anderen Worten: Wir brauchen uns in der Regel keine Gedanken über die Begrenzung der Anzahl der Blöcke zu machen. die von unserem Bundler ausgegeben werden.

Alle gängigen Browser unterstützen HTTP/2. Die Chrome- und Next.js-Teams wollte herausfinden, ob die Anzahl der Anfragen durch Aufteilung der einzelnen "Commons" von Next.js erhöht würde. Set die die Ladeleistung beeinträchtigen würden. Sie begannen mit der Messung der Leistung einer einzelnen Website erzielen und gleichzeitig die maximale Anzahl paralleler Anfragen mithilfe der Funktion maxInitialRequests Property.

Seitenaufbauleistung bei steigender Anzahl von Anfragen

Bei durchschnittlich drei Durchläufen mehrerer Tests auf einer einzelnen Webseite load, start-render und First Contentful Paint-Zeiten blieben alle ungefähr gleich, wenn das maximale Anzahl der Anfragen (von 5 bis 15). Interessanterweise stellten wir nur einen geringen Leistungsaufwand fest. nach einer aggressiven Aufteilung auf Hunderte von Anfragen.

Leistung beim Seitenaufbau mit Hunderten von Anfragen

Es zeigte sich, dass das Halten eines zuverlässigen Schwellenwerts (20–25 Anfragen) das richtige Gleichgewicht war. zwischen Ladeleistung und Caching-Effizienz. Nach einigen Ausgangstests wurden 25 als Anzahl der maxInitialRequest.

Wenn Sie die maximale Anzahl paralleler Anfragen ändern, wurden mehrere und die sinnvolle Trennung für die einzelnen Einstiegspunkte reduzierten die nicht benötigten Code für dieselbe Seite generiert.

Reduzierung der JavaScript-Nutzlast mit zunehmender Aufteilung

Bei diesem Test ging es nur darum, die Anzahl der Anfragen zu ändern, um zu ermitteln, negative Auswirkungen auf die Seitenladeleistung. Laut den Ergebnissen sollte maxInitialRequests auf 25 auf der Testseite war optimal, da dadurch die Größe der JavaScript-Nutzlast reduziert wurde, ohne dass dies zu einer Verlangsamung führte. auf der Seite nach unten. Die Gesamtmenge an JavaScript, die zur Bereitstellung der Seite erforderlich war, blieb weiterhin bestehen. Das erklärt, warum sich die Ladegeschwindigkeit aufgrund der geringeren die Menge an Code.

Webpack verwendet 30 KB als Standard-Mindestgröße für einen zu erstellenden Block. Das Koppeln eines Der maxInitialRequests-Wert von 25 mit einer Mindestgröße von 20 KB führte stattdessen zu einem besseren Caching.

Größenreduzierungen mit detaillierten Blöcken

Viele Frameworks, einschließlich Next.js, nutzen für die Injektion clientseitiges Routing (von JavaScript verarbeitet). neuere Skript-Tags für jeden Routenübergang. Aber wie werden diese dynamischen Blöcke bei der Build-Erstellung vorab festgelegt?

Next.js bestimmt anhand einer serverseitigen Build-Manifestdatei, welche ausgegebenen Blöcke von unterschiedlichen Einstiegspunkten. Um auch dem Kunden diese Informationen zur Verfügung zu stellen, wird eine clientseitige Kurzfassung angezeigt. Build-Manifestdatei erstellt wurde, um alle Abhängigkeiten für jeden Einstiegspunkt zuzuordnen.

// Returns a promise for the dependencies for a particular route
getDependencies (route) {
  return this.promisedBuildManifest.then(
    man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
  )
}
Ausgabe mehrerer freigegebener Blöcke in einer Next.js-Anwendung.

Diese neuere, detaillierte Aufteilungsstrategie wurde erstmals in Next.js hinter einem Flag eingeführt, wo sie auf einem der ersten Nutzer. Bei vielen Unternehmen sank die Anzahl der JavaScript-Elemente, die für ihre gesamte Website:

Website JS-Gesamtänderung Unterschied in %
https://www.barnebys.com/ -238 KB -23%
https://sumup.com/ -220 KB -30%
https://www.hashicorp.com/ -11 MB -71%
JavaScript-Größenreduzierungen – für alle Routen (komprimiert)

Die endgültige Version wurde standardmäßig in Version 9.2 ausgeliefert.

Gatsby

Bei Gatsby verfolgte das Unternehmen den gleichen Ansatz wie die Verwendung einer nutzungsbasierten Heuristik zum Definieren gängiger Module:

config.optimization = {
  …
  splitChunks: {
    name: false,
    chunks: `all`,
    cacheGroups: {
      default: false,
      vendors: false,
      commons: {
        name: `commons`,
        chunks: `all`,
        // if a chunk is used more than half the components count,
        // we can assume it's pretty global
        minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2,
      },
      react: {
        name: `commons`,
        chunks: `all`,
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
      },

Durch die Optimierung der Webpack-Konfiguration für eine ähnliche, detaillierte Aufteilungsstrategie erhebliche JavaScript-Reduzierung bei vielen großen Websites festgestellt:

Website JS-Gesamtänderung Unterschied in %
https://www.gatsbyjs.org/ -680 KB -22%
https://www.thirdandgrove.com/ -390 KB -25 %
https://ghost.org/ -1,1 MB -35%
https://reactjs.org/ -80 KB -8 %
JavaScript-Größenreduzierungen – für alle Routen (komprimiert)

In der Öffentlichkeitsarbeit erfahren Sie, wie sie implementiert diese Logik in ihrer Webpack-Konfiguration, die standardmäßig in Version 2.20.7 ausgeliefert wird.

Fazit

Das Konzept des Versands von detaillierten Blöcken ist nicht spezifisch für Next.js, Gatsby oder sogar Webpack. Alle sollten in Erwägung ziehen, die Aufteilungsstrategie ihrer Anwendung zu verbessern, wenn sie einem großen „Commons“-Prinzip folgt. Bundle-Ansatz, unabhängig vom verwendeten Framework oder Modul-Bundler.

  • Wenn Sie die gleichen Segmentierungsoptimierungen auf eine einfache React-Anwendung anwenden möchten, Sehen Sie sich dieses React-Beispiel App. Dabei wird ein vereinfachte Version der detaillierten Aufteilungsstrategie und hilft Ihnen, mit Logik auf Ihrer Website.
  • Für Rollup werden Blöcke standardmäßig im Detail erstellt. Sehen Sie sich manualChunks, wenn Sie manuell Änderungen vornehmen möchten konfigurieren Sie das Verhalten.