Startseite / Artikel

Anpassung der neuen Chunking-Steuerungen von Turbopack in Next.js 16.3

Ein praktischer Überblick über die neue turbopackChunking-Einstellung in Next.js 16.3, bei dem erklärt wird, wie maxChunkCountPerGroup und generateComponentChunks die Größe des Bundles sowie das Caching beeinflussen.

6 Min. Lesezeit1482 Wörter

Es war 16:40 an einem Freitag, genau in dem Moment, in dem man den Laptop für das Wochenende schließt, als eine routinemäßige Bereitstellung einer Dashboard-App plötzlich nicht mehr routinehaft verlief. Ein Build, der normalerweise in 40 Sekunden abgeschlossen wurde, dauerte nun etwas über drei Minuten, und das Client-Bundle für die Hauptroute wuchs um fast 300 KB. Keine Routen waren verändert worden, und es wurden keine neuen Abhängigkeiten hinzugefügt, die das erklären könnten. Die einzige Veränderung in dieser Woche war ein kleiner Versionsaufstieg von Next.js. Was folgte, war ein Abend, an dem ich stundenlang auf die Netzwerkansicht starrte und beobachtete, wie vier riesige JavaScript-Dateien nacheinander geladen wurden, obwohl früher ein Dutzend kleinere Dateien diese Aufgabe erledigten.

Die Ursache war kein Fehler. Es lag daran, dass die Chunking-Logik von Turbopack genau so funktionierte, wie sie konzipiert war – nur eben nicht auf eine Weise, die den Erwartungen entsprach.

Warum das Chunking-Problem nie wirklich gelöst wurde

Dies ist ein Problem, mit dem Bundler seit etwa zehn Jahren zu kämpfen haben. Webpacks SplitChunksPlugin stellte Einstellungen wie minSize, maxSize und cacheGroups zur Verfügung, die fast niemand wirklich aus den Grundprinzipien heraus verstand – sie wurden größtenteils aus einem Stack Overflow-Thread aus dem Jahr 2019 kopiert. Der zugrundeliegende Kompromiss bleibt bestehen: Die Zusammenfassung in weniger, größere Blöcke verringert die HTTP-Anfragen, schadet aber dem Caching, da eine einzige CSS-Änderung einen 500kb großen Bundle ungültig machen kann. Die Aufteilung in viele kleinere Blöcke verbessert die Granularität des Cachens, erhöht aber die Anzahl der Round Trips, was bei einer langsamen Verbindung schnell spürbar wird.

Turbopack übernahm dieses gleiche Gleichgewichtsproblem und neigte bis vor Kurzem stark zur Konsolidierung. Das ist eine vernünftige Wahl für eine Einseiten-Sitzung, bei der die meisten Ressourcen ohnehin nur einmal heruntergeladen werden. Es wirkt sich jedoch nachteilig aus, wenn Benutzer wiederholt durch eine große Anwendung navigieren, da zuvor im Cache gespeicherter Code immer wieder zusammen mit dem neuen Code heruntergeladen wird, der sich daneben befindet.

Was hat sich in 16.3 geändert?

Mit Next.js 16.3 ist das Mischverhalten nicht mehr hinter einer einzigen, unklaren Heuristik verborgen, sondern wird nun direkt über die Konfiguration gesteuert. Ein neuer turbopackChunking-Block wurde hinzugefügt, und die beiden wichtigsten Einstellungen sind maxChunkCountPerGroup sowie maxMergeChunkSize.

// next.config.js
module.exports = {
  experimental: {
    turbopackChunking: {
      maxChunkCountPerGroup: 40, // default
      maxMergeChunkSize: 200_000, // bytes, default
    },
  },
};

Ein niedrigerer Wert für maxChunkCountPerGroup führt dazu, dass Turbopack aggressiver zusammenführt: Es werden weniger Anfragen pro Seitenladung gestartet, doch die Cachenspeicherung über verschiedene Navigationen hinweg ist schwächer. Ein höherer Wert erzeugt kleinere, zahlreichere Blöcke, die auch dann unverändert bleiben, wenn sich unabhängige Teile der Anwendung ändern. maxMergeChunkSize begrenzt die Größe eines zusammengeführten Blocks, bevor Turbopack aufhört, zusätzlichen Code hinzuzufügen – das ist nützlich, wenn eine einzige große Abhängigkeit vom Anbieter dazu neigt, alles um sich herum aufzunehmen.

Bisher existierten diese Einstellungen nicht. Man erhielt einfach das, was die Standardheuristik ergab, und musste sich damit abfinden.

Die Option zur Komponentenblöckierung

Eine bemerkenswertere Erweiterung ist generateComponentChunks, das Turbopacks Fähigkeit zur Blöckierung auf einzelne Komponenten ausdehnt, anstatt bei den Grenzen auf Route-Ebene zu stoppen.

module.exports = {
  experimental: {
    turbopackChunking: {
      generateComponentChunks: true,
    },
  },
};

Durch Aktivierung wird veranlasst, dass Turbopack sowohl die zusammengeführten als auch die unzusammengeführten Versionen der Blöcke ausgibt, wodurch die Laufzeit bei der Ladenzeit entscheiden kann, welche Version heruntergeladen werden soll – basierend darauf, was der Browser bereits im Cache hat. Wenn ein gemeinsamer Komponente aus einem früheren Seitenbesuch im Cache gespeichert wurde, kann die Laufzeit stattdessen den kleineren, unzusammengeführten Block anfordern, anstatt einen zusammengeführten Bundle herunterzuladen, der bereits vorhandenen Code dupliziert. Dies ist ein grundlegend anderer Ansatz als das Vorausrechnen der idealen Blockgröße im Voraus – es handelt sich um den ersten Fall, in dem ein Bundler diese Entscheidung auf die Anfragezeit verschiebt, anstatt sie bereits bei der Kompilierung festzulegen.

Was wir tatsächlich gesehen haben

Ein Team, das dies in einer Dashboard-Anwendung getestet hat, aktivierte generateComponentChunks und beobachtete, was in den folgenden Tagen geschah. Die anfängliche Seitenladezeit änderte sich kaum und verringerte sich nur um etwa 2 %. Bei wiederholten Navigationsvorgängen, die den Großteil der tatsächlichen Nutzung einer Anwendung wie eines Dashboards ausmachen, sank die durchschnittliche Menge an übertragenem JS pro Navigation um etwa 35 %. Das ist der Kernwert dieser Lösung: Es handelt sich nicht um eine Funktion, die darauf abzielt, die erste Ladezeit zu verkürzen, sondern um eine Lösung für den Fall, dass jemand zehn Minuten lang ununterbrochen in der Anwendung navigiert. Die meisten im Internet verfügbaren Vergleiche zur Bundle-Größe messen ein völlig anderes Szenario, das nicht widerspiegelt, wie Anwendungen mit umfangreicher interner Navigation tatsächlich genutzt werden.

Das gleiche Team überprüfte außerdem maxChunkCountPerGroup für eine Marketing-Website, die sich im selben Monorepo befand, und verringerte den Wert von 40 auf 24. Auf dieser Website kommen hauptsächlich Einzel-Seiten-Sessions vor, wodurch die durch feinere Aufteilung in Blöcke entstehenden zusätzlichen Netzwerkanfragen reine Overhead-Belastung ohne Nutzen darstellten. Dasselbe zugrunde liegende Tool, gegensätzliche Einstellungen – und beide Entscheidungen waren für ihre jeweiligen Kontexte richtig. Das ist wohl die eigentliche Lektion: Es gab niemals eine einzige, universell richtige Strategie zur Aufteilung in Blöcke; Turbopack hat lediglich diese Entscheidung ohne Nachfrage für einen getroffen.

Vergleich mit den alten Webpack-Einstellungen

Für alle, die jahrelang mit splitChunks.cacheGroups zu kämpfen hatten, ist das nächstliegende Äquivalent in diesem neuen System maxMergeChunkSize. Im Gegensatz zu vielen anderen Bezeichnungen in Webpack verhält sich dieser Wert genau so, wie es der Name vermuten lässt. Die Konfigurationsmöglichkeiten von Webpack boten Dutzende nur unvollständig dokumentierter Einstellungen, die auf Weise miteinander interagierten, die niemand vollständig verstand. Viele Teams mussten schließlich Konfigurationsdateien von längst abgegangenen Entwicklern übernehmen, die alle zu ängstlich waren, sie zu verändern. Der Ansatz von Turbopack ist bewusst enger gefasst: Statt Dutzender Einstellungen gibt es nur zwei oder drei, was zwar etwas an Flexibilität kostet, wenn es um wirklich ungewöhnliche Ausnahmefälle geht, aber auch die Zeit zwischen dem Erkennen, dass „unsere Bundle-Datei seltsam aussieht“, und dem Wissen darüber, welche Zahl angepasst werden muss, verkürzt. Dieser Kompromiss ist als akzeptabel zu betrachten – besonders für alle, die es lieber vermeiden möchten…

Wir werden wohl nie wieder eine solche mehrjährige cacheGroups-Konfiguration haben.

Es gibt noch einen weiteren Unterschied, den man besonders hervorheben sollte. Webpack zwang einen größtenteils dazu, zur Zeit der Kompilierung über das Chunking nachzudenken, in eine stats.json-Ausgabe zu schauen und zu versuchen, vorherzusagen, was tatsächlich im Browser-Cache eines echten Besuchers gespeichert sein würde. Turbopacks Chunking auf Komponentenebene verlagert einen Teil dieser Überlegungen auf die Zeit des Anfragen, wobei der Laufzeitumgebung möglich ist, den tatsächlichen Cache-Zustand zu überprüfen, anstatt im Voraus zu raten. Das ist ein echter architektonischer Wandel, nicht nur eine umbenannte Konfigurationsflagge.

Der Haken

Diese Funktion ist weiterhin als experimentell gekennzeichnet, und die Dokumentation macht das von Anfang an deutlich. Ein Team stieß auf einen Fall, in dem eine dynamisch importierte Charting-Bibliothek auf eine Weise geteilt wurde, die während eines Deployments in der Produktion kurzfristig den Fehler „Failed to load chunk“ auslöste – das bekannte Problem mit veralteten Chunk-Referenzen, das im Grunde jeden Bundler mit Code-Splitting-Funktion betroffen hat. Ein harter Neuladen löste das Problem für die betroffenen Nutzer, doch es dient als nützliche Erinnerung daran, dass eine feinere Aufteilung in Chunks den Angriffsflächen für diesen spezifischen Fehlermodus eher Vorschub leistet, anstatt sie zu verringern.

Außerdem gibt es das Problem der Anpassung dieser Einstellungen ohne echte Traffic-Daten – was im Grunde auf Spekulation beruht. Es lohnt sich, dem Drang zu widerstehen, bei einem neuen Projekt sofort die Standardwerte zu ändern, solange man keine tatsächlichen Navigationsanalysen hat, die zeigen, ob die Besucher eher einmalig kommen oder die Art sind, die in einer einzigen Sitzung zwischen einem Dutzend oder mehr Routen wechseln. Wenn diese Daten noch nicht vorhanden sind, ist das ein guter Grund, sie bereits vor der Anpassung jeglicher Konfigurationen zu sammeln. Blindes Anpassen birgt das echte Risiko, eine schlechtere Cache-Hit-Rate zu erzielen als mit den Standardwerten – und ein solcher Rückschritt bleibt unbemerkt. Niemand erstattet einen Fehlerbericht mit dem Titel „Die Navigation wirkte etwas langsamer“, sie kommen einfach nicht mehr zurück.

Sollte man es also aktivieren?

Im Dashboard-Szenario lautete die Antwort ja, und die Einstellung blieb aktiviert. Bei der Marketing-Website wurde nur eine einzige Zahl angepasst, und das war’s auch schon. Nichts davon behebt eine grundlegend fehlerhafte Anwendung, und wenn Ihr Paket aufgrund einer eigentlich nicht benötigten Abhängigkeit überdimensioniert ist, wird keine Chunking-Strategie der Welt Sie aus diesem Fehler retten. Was diese Funktion bietet, ist eine gezielte Lösung für ein spezifisches Problem – was einen erfrischenden Kontrast zu vielen der Build-Tooling-Funktionen darstellt, die in diesem Jahr veröffentlicht werden. Dieser Urteilsspruch hat Gewicht, vor allem von jemandem, der mehr Abende damit verbracht hat, genau diese Art von Fehlern zu beheben, als ihm lieb ist.

Verwandte Artikel

  • Next.js Route Handler zu einer gezielten BFF-Schicht umwandeln — Erfahren Sie, was das Backend-for-Frontend-Muster löst, warum es in Next.js-Anwendungen wieder an Bedeutung gewinnt und wie man vermeidet, dass Route Handler zu überdimensionierten „Gott-Objekten“ werden.