Ressourcenerkennung statt Übertragung: Vorausladen von React-Apps über HTTP/3
Erfahren Sie, warum HTTP/3 die späte Ressourcenerkennung zum eigentlichen Engpass in React-Apps macht und wie preloadModule, preinit, Early Hints sowie Chunking dieses Problem beheben.
Die Aufrüstung eines Servers auf HTTP/3 beschleunigt die Übertragung der Bytes, doch viele React-Anwendungen fühlen sich danach kaum schneller an. Der übliche Grund dafür ist, dass der langsame Schritt im Prozess nie die eigentliche Übertragung war – sondern vielmehr der Moment, in dem der Browser erfuhr, dass überhaupt eine Ressource existiert. Diese Anleitung trennt diese beiden Probleme voneinander, zeigt auf, wo QUIC tatsächlich hilft, und führt durch die Werkzeuge, die die Entdeckung von Ressourcen früher ermöglichen: die React 19 Resource-APIs, das zielorientierte Vorauladen von Modulen, 103 Early Hints, streambasiertes SSR sowie eine auf multiplexierten Transport geeignete Chunking-Strategie.
Ein Ablauf, der gut aussieht, aber dennoch langsam wirkt
Stellen Sie sich ein Team vor, das über eine 4G-Verbindung einen Analytics-Dashboard analysiert. Jeder wichtige Datenblock ist bereits vorgegeladen, der Netzwerkfluss erscheint ordentlich – doch die Zeit bis zur Interaktivität hält sich hartnäckig unter dem Zielwert. Jeder Block wird schnell heruntergeladen, sobald er beginnt, sodass HTTP/3 offensichtlich seine Aufgabe erfüllt.
Wenn man genauer hinschaut, ändert sich das Muster. Die Downloads sind schnell, doch die Anfragen beginnen spät. React muss heruntergeladen, gestartet, ausgeführt und gerendert werden, bis es eine sogenannte „lazy Boundary“ erreicht – erst dann erfährt der Browser, dass Dashboard.js benötigt wird. Hunderte von Millisekunden vergehen, bevor überhaupt das erste Byte dieses Blocks angefordert wird. Die Verzögerung liegt oberhalb des Netzwerks, in einem Schritt, der in den meisten Leistungsüberprüfungen nie separat aufgeführt wird: die Ressourcenerkundung im Gegensatz zur Ressourcenlieferung.
Lieferung und Erkundung sind zwei verschiedene Probleme
Es hilft, „Laden einer Ressource“ in zwei Fragen aufzuteilen:
- Übertragungsgeschwindigkeit: Wie schnell gelangen die Bytes nach der Anfrage vom Server zum Browser?
- Entdeckungszeitpunkt: Wann erkennt der Browser überhaupt, dass er diese Bytes benötigt?
Eine aus einer Stylesheet referenzierte Web-Schrift macht den Unterschied deutlich. Die Kette sieht so aus:
HTML → CSS → @font-face rule → font request
Egal wie schnell die Verbindung ist, der Anfrage nach der Schriftart kann erst gestellt werden, nachdem die HTML-Datei eingetroffen ist, das CSS heruntergeladen und analysiert wurde sowie die @font-face-Regel gefunden und mit dem dargestellten Text abgeglichen wurde. Das ist eine Verzögerung durch die Entdeckung der Schriftart. Ein <link rel="preload">-Tag beschleunigt den Download der Schriftart um keinen Millisekundenbruchteil; er ermöglicht lediglich, dass der Browser die Anfrage früher sendet. Fast alles in den restlichen Teilen dieses Leitfadens ist eine Variation dieser Idee.
Welches Tool beantwortet welche Frage
Jede unten besprochene Technologie zielt auf eine andere Phase des Ladeprozesses ab:
preconnect: Mit welchen Quellen sollte der Browser bereits vor dem Senden einer Anfrage in Kontakt treten?preload: Welche spezifische Datei wird vom Browser von selbst zu spät gefunden?
preloadModule und modulepreload: Welche ES-Modul-Blöcke sollten vor der Ausführung heruntergeladen und kompiliert werden?preinit und preinitModule: Welche Ressourcen müssen nicht nur heruntergeladen, sondern bereits frühzeitig angewendet oder ausgeführt werden?prefetch: Was wird voraussichtlich bei der nächsten Navigation benötigt, mit niedriger Priorität?Nur der letzte Punkt betrifft den Transport. Alles Weitere bezieht sich auf die Erkennung, Zeitpunkt oder Planung – und dieser Anteil gibt einen guten Überblick darüber, wo die übrigen Vorteile in der Regel liegen.
Wo das Multiplexing von HTTP/2 versagt hat
Unter HTTP/1.1 erreichten Browser den Parallelismus, indem sie pro Host mehrere TCP-Verbindungen öffneten – in der Regel begrenzt auf etwa sechs – wobei jede Verbindung jeweils nur eine Ressource transportierte. HTTP/2 ersetzte dies durch eine einzige Verbindung, die viele Ströme abwechselnd überträgt:
HTTP/1.1 HTTP/2
──────────────── ────────────────────
TCP conn 1 → JS One connection
TCP conn 2 → CSS ├── Stream A: JS
TCP conn 3 → Font ├── Stream B: CSS
TCP conn 4 → Image ├── Stream C: Font
└── Stream D: Image
Das war ein echter Fortschritt, doch HTTP/2 basierte weiterhin auf TCP, das eine streng geordnete Übertragung eines einzigen Byte-Streams verspricht. Es hat keine Ahnung davon, dass einige Bytes zum JavaScript-Stream und andere zum Schriftsatz-Stream gehören. Wenn ein Paket fehlt, hält TCP alles danach zurück, bis die Wiederverteilung eintrifft – selbst dann, wenn das fehlende Paket zum Stream C gehörte und die anderen drei Ströme damit nichts zu tun hatten.
Das ist das TCP-Head-of-Line-Blocking. In stabilen Netzwerken ist es selten bemerkbar; in instabilen mobilen Verbindungen ist es der Hauptgrund dafür, dass das Multiplexing von HTTP/2 seine Versprechen nie vollständig erfüllen konnte.
Welche Änderungen bringt QUIC im Rahmen von HTTP/3?
HTTP/3 ersetzt TCP durch QUIC, das auf UDP basiert und die Verschlüsselung selbst übernimmt:
HTTP/2 HTTP/3
──────── ────────
HTTP/2 HTTP/3
↓ ↓
TCP QUIC
↓ ↓
TLS UDP
↓ ↓
IP IP
Unabhängige Ströme beseitigen das HOL-Blockieren auf Transportebene
Weil QUIC die Ströme innerhalb des Transportprotokolls verwalten lässt, wird jeder Stream unabhängig wiederhergestellt. Ein verlorenes Paket blockiert nur den Stream, zu dem es gehörte:
Stream A ──────────────────── ✓
Stream B ──────────────────── ✓
Stream C ──────── X ─ retry
Stream D ──────────────────── ✓
Die Ströme A, B und D fließen weiter, während C auf seine Wiederversendung wartet. Der gemessene Effekt ist am größten genau dort, wo TCP am meisten litt. Eine im Juli 2025 veröffentlichte Catchpoint-Studie, die in sechs Ländern durchgeführt wurde, zeigte, dass auf stark verlustbehafteten Verbindungen die mittlere Zeit bis zum ersten Byte um 41,8 % sank. Interne Tests bei Wix ergaben eine 33 % schnellere Verbindungsherstellung sowie eine Verbesserung des p75 LCP um 20 %. Auf einer stabilen Breitbandverbindung schrumpft der Vorteil gegenüber HTTP/2 auf etwa 5 %. Diese Asymmetrie ist an sich aufschlussreich: Die Vorteile treten dort auf, wo zuvor das HOL-Blocking Probleme verursachte. Betrachten Sie diese Zahlen als Auszüge aus diesen Studien und nicht als Garantien für Ihren Datenverkehr – messen Sie lieber bei Ihren eigenen Nutzern.
Kürzere Round-Trips bis zum ersten Byte
Durch HTTP/2 über TCP muss für eine neue Verbindung zunächst die TCP-Handshake-Phase sowie anschließend eine separate TLS-Verhandlung durchgeführt werden, was insgesamt zwei Round-Trips bedeutet, bevor Daten an die Anwendung übertragen werden. QUIC integriert den TLS 1.3-Austausch in die Verbindungserrichtung und erledigt beides in einem einzigen Round-Trip. Bei wiederkehrenden Besuchern ermöglicht die 0-RTT-Wiederaufnahme, dass verschlüsselte Anfragedaten zusammen mit dem Eröffnungsdatenpaket übertragen werden. Auf einer interkontinentalen Verbindung mit 150 ms Verzögerung spart das bei jeder neuen Verbindung etwa 150 bis 300 ms ein. Beachten Sie, dass 0-RTT-Daten wiedergegeben werden können, weshalb Server sie in der Regel nur für idempotente Anfragen wie das Abrufen statischer Ressourcen akzeptieren.
Verbindungen, die einen Netzwerkwechsel überstehen
TCP identifiziert eine Verbindung durch die Kombination aus Quell- und Ziel-IP-Adressen sowie Ports. Wenn ein Telefon vom Wi-Fi auf das Mobilfunknetz wechselt, ändert sich seine Adresse und die TCP-Verbindung wird unterbrochen. QUIC hingegen verwendet eine nicht transparente Connection ID, sodass die Sitzung auf den neuen Weg migrieren kann. Ein vorab geladener Route-Teil muss nicht neu gestartet werden, nur weil ein Pendlerzug den Bahnhof verlässt.
Macht HTTP/3 das Vorausladen überflüssig?
Nicht unbedingt, und das Verständnis dafür ist der Kern des gesamten Themas. HTTP/3 optimiert, wie Ressourcen *übertragen* werden; das Vorausladen optimiert, wie früh sie *angefordert* werden. Beides wirkt an unterschiedlichen Stellen in der Kette:
Browser
│
│ ← "I don't know I need this yet"
↓
Resource discovery ← preload operates here
│
↓
Request
│
↓
QUIC transport ← HTTP/3 operates here
│
↓
Server
Ein Transportprotokoll kann nichts abrufen, was noch niemand angefordert hat. Tatsächlich macht ein schnelleres Transportverfahren späte Entdeckungen auffälliger. Angenommen, die Übertragungszeit eines Datensatzes sinkt von 300 ms auf 80 ms. Eine zuvor teilweise im Gesamtwert verborgene Entdeckungsverzögerung von 400 ms macht nun den größten Teil aus. Der Engpass hat sich verschoben, anstatt zu verschwinden.
Warum React Abhängigkeiten vor dem Browser versteckt
Einfache HTML-Ressourcen wie <img>-Quellen und <link>-Stylesheets werden früh gefunden, da der Browser-Parser (sowie sein spekulativer Vorauslade-Scanner) sie beim Lesen der Dokumente erkennt. Bei clientseitig renderndem React kommt es zu einer viel längeren Kette, bevor einige Abhängigkeiten sichtbar werden:
HTML → main.js → React executes → render → lazy() → discover Dashboard.js → download → render
Mit React.lazy() erfolgt die Importierung nach der Ausführung des JavaScript-Code. Der Browser kann nicht erkennen, dass Dashboard.js existiert, bevor der Haupt-Bundle heruntergeladen, analysiert, kompiliert und ausgeführt wurde sowie React weit genug gerendert hat, um zum lazy Component zu gelangen. Bei einem ersten Besuch von einem langsamen Telefon kann das bedeuten, dass mehrere Sekunden vergehen, bevor die Anfrage nach dem Chunk gestartet wird.
const Dashboard = lazy(() => import("./Dashboard"));
// The browser has no idea Dashboard.js exists
// until this renders. And it only renders after
// React has fully bootstrapped.
Suspense verbessert die Wartezeit, nicht die Entdeckung
Eine häufige Fehlvorstellung ist, dass das Einpacken des Components in Suspense das Problem löst. Es ändert nichts daran, wann der Chunk angefordert wird:
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
Was Suspense bietet, ist Koordination: Solange der fehlende Komponententeil noch nicht geladen wurde, zeigt React eine Ersatzanzeige an, anstatt den gesamten Renderbaum zu blockieren. Das ist wichtig für die wahrgenommene Qualität, stellt aber kein Mittel zur Vorhersage von Ressourcen dar. Die Anfrage wird dennoch zu demselben späten Zeitpunkt gestartet. HTTP/3 kann die Daten danach effizient übertragen, hat jedoch keinen Einfluss darauf, wie lange es dauert, bis sie ankommen.
React 19s Ressourcen-APIs und ihre tatsächliche Funktion
React 19 bringt in react-dom eine Reihe von Funktionen mit, die es Komponenten ermöglichen, Ressourcenhinweise genau zu dem Zeitpunkt während des Renderings in den Scheduler des Browsers einzuspeisen, an dem der Bedarf bekannt wird. Es handelt sich dabei um mehr als nur leichte Umhüllungen von HTML-Tags: React entfernt Duplikate und kann sie während des Server-Renderings bereits in den Dokumentenkopf einfügen, damit der Browser sie frühzeitig erhält.
preconnect: Eine Quelle vorbereiten
Verwenden Sie preconnect, wenn eine Anfrage zu einer anderen Quelle sicherlich bald folgen wird. Dadurch werden die DNS-Abfrage, die Verbindungsaufbau sowie der TLS-Handshake im Voraus gestartet.
import { preconnect } from "react-dom";
// Call this when you know a cross-origin
// request is coming - not just "might be coming."
preconnect("https://cdn.example.com");
Reservieren Sie diese Funktion für Quellen, mit denen Sie definitiv in Kontakt treten werden. Jede vorbereitete Verbindung erfordert Rechenleistung auf Client und Server, und eine ungenutzte Verbindung wird einfach weggeworfen.
preload: Eine bestimmte Datei frühzeitig herunterladen
preload weist den Browser an, eine bekannte Ressource ohne Ausführung oder Anwendung herunterzuladen. Schriftarten sind ein klassisches Beispiel, da sie sonst hinter der CSS-Parsing-Phase versteckt bleiben:
import { preload } from "react-dom";
// Font hidden behind CSS - the browser won't
// find this until it processes @font-face.
// Preload surfaces it earlier.
preload("/fonts/inter.woff2", {
as: "font",
crossOrigin: "anonymous",
});
Achten Sie auf die Option crossOrigin: „anonymous“. Schriftarten werden immer im CORS-Modus angefordert, weshalb ein Font-Preload ohne diese Option zu einer Anfrage führt, die nicht mit der tatsächlichen übereinstimmt, und der Browser letztendlich die Datei zweimal herunterlädt.
preloadModule: Abrufen und Kompilieren eines ES-Moduls
preloadModule dient demselben Zweck wie bei ES-Modulen, geht jedoch einen Schritt weiter: Das Modul wird heruntergeladen, analysiert und kompiliert, anschließend im Module-Map gespeichert, sodass es sofort ausgeführt werden kann, sobald ein import() danach fragt.
import { preloadModule } from "react-dom";
// Use this for lazy route chunks you know
// are likely to be needed soon.
preloadModule("/assets/Dashboard-abc123.js");
Dies eignet sich ideal für „lazy“ Route-Blöcke, die voraussichtlich bald benötigt werden.
preinit und preinitModule: Abrufen und In Betrieb Nehmen
preinit und preinitModule sind die leistungsstärkeren Varianten. Sie holen die Ressource herunter und sorgen gleichzeitig dafür, dass sie wirksam wird: Eine Stylesheet wird eingefügt und angewendet, sowie ein Skript ausgeführt, sobald es ankommt.
import { preinit } from "react-dom";
// You don't just want this downloaded -
// you want it applied before render.
preinit("/styles/app.css", { as: "style" });
Der Abstand zwischen den beiden Familien ist für CSS am wichtigsten. Eine vorher geladene Stylesheet-Datei wird heruntergeladen, aber nicht angewendet. Wenn diese Stylesheet-Datei vor dem ersten Zeichnen benötigt wird, haben Sie den Download zwar früher durchgeführt, doch die Darstellung wartet weiterhin darauf, dass etwas sie tatsächlich einfügt. preinit umfasst beide Schritte. Bei Skripten gilt die entgegengesetzte Vorsichtsmaßnahme: Nur preinit-Code, der sofort ausgeführt werden darf.
Auslösen von preloadModule aufgrund der Benutzerabsicht
Das Aufrufen von preloadModule für jeden Pfad beim Start verschwendet Bandbreite. Der beste Zeitpunkt ist, wenn die Benutzerabsicht sichtbar wird – was in der Regel bedeutet, dass ein Zeiger auf einen Navigationsschlusspunkt fällt oder die Tastaturfokussierung kurz vor dem Klick auf einen solchen Link gerichtet wird.
Der untenstehende Komponente verbindet alles miteinander. Er rendernt einen normalen Anker, sodass der Link auch ohne JavaScript funktioniert, ruft preloadModule sowohl bei onMouseEnter als auch bei onFocus auf, damit auch Tastaturnutzer davon profitieren, und überlässt die eigentliche Navigation an React Routers navigate:
import { preloadModule } from "react-dom";
import { useNavigate } from "react-router-dom";
function NavLink({ to, chunkPath, children }) {
const navigate = useNavigate();
return (
<a
href={to}
onMouseEnter={() => preloadModule(chunkPath)}
onFocus={() => preloadModule(chunkPath)}
onClick={(e) => {
e.preventDefault();
navigate(to);
}}
>
{children}
</a>
);
}
// Usage
<NavLink to="/dashboard" chunkPath="/assets/Dashboard-abc123.js">
Dashboard
</NavLink>
Die Zeit zwischen Hover und Klick liegt in der Regel bei etwa 100 bis 400 ms. Über HTTP/3 kann ein mittelgroßer Datenblock oft innerhalb dieses Zeitraums übertragen werden: Bei 10 Mbps benötigen 150 KB etwa 120 ms. Bis zum eigentlichen Klick ist der Modul bereits im Module-Map kompiliert, die Lazy-Lading-Grenze wird sofort gelöst, und der Suspense-Fallback tritt nie auf.
Was der onMouseEnter-Handler erreicht, ist etwas, was kein Transportprotokoll leisten kann: Er wandelt ein Signal der Absicht in Kenntnisse über eine Ressource um, noch bevor eine Navigation angefordert wird. HTTP/3 kümmert sich anschließend um den effizienten Transfer. Jede Schicht erledigt ihre eigene Aufgabe.
Zwei praktische Hinweise. Erstens muss chunkPath der gehashte Dateinamen sein, den Ihr Bundler tatsächlich erzeugt hat; daher sollte er in einem echten Projekt aus dem Build-Manifest stammen und nicht manuell eingegeben werden. Zweitens haben Touch-Geräte kein Hover-Verhalten, weshalb Sie bei mobiler Navigation onTouchStart oder triggerbasierte Lösungen, die auf dem Sichtbereich beruhen, in Betracht ziehen sollten.
Vorabladeung auf Bundler-Ebene
Für großflächige, weniger wichtige Vorabladeungen in Zeiten der Inaktivität unterstützt webpack einen speziellen Kommentar innerhalb der dynamischen Importe:
const Dashboard = lazy(
() => import(/* webpackPrefetch: true */ "./Dashboard")
);
Achten Sie darauf, dass webpackPrefetch zu einem Hinweis in Form von <link rel="prefetch"> führt, den der Browser als Arbeit mit niedriger Priorität in Zeiten stiller Aktivität für eine mögliche zukünftige Navigation betrachtet. Das ist ein anderer Signaltyp als das hochpriorisierte preload oder modulepreload, das für Ressourcen verwendet wird, die sofort benötigt werden.
Vite verfolgt einen automatischeren Ansatz und erzeugt für Sie modulepreload-Links. Bei webpack benötigen Sie entweder einen speziellen Kommentar oder ein Plugin. Die native HTML-Form dieses Hinweises sieht wie folgt aus:
<!-- Vite generates these for lazy chunks automatically -->
<link rel="modulepreload" href="/assets/Dashboard-abc123.js">
<link rel="modulepreload" href="/assets/vendor-react-def456.js">
Genau genommen enthält das von Vite erzeugte HTML modulepreload-Links für den Eingangsbereich sowie seine statischen Importe. Für dynamisch importierte Bereiche fügt Vites Laufzeit-Hilfsfunktion zu dem Zeitpunkt, an dem import() aufgerufen wird, preload-Links für deren Abhängigkeiten ein, sodass der Bereich und seine Importe parallel statt nacheinander geladen werden. Prüfen Sie die Build-Dokumentation Ihrer Vite-Version, um das genaue Verhalten zu erfahren.
Verglichen mit einem allgemeinen rel="preload" ermöglicht modulepreload es dem Browser, das Modul sobald wie möglich zu analysieren und zu kompilieren, anstatt bis zum Ausführungszeitpunkt zu warten. Über HTTP/3 werden mehrere solcher Hinweise über unabhängige QUIC-Ströme übertragen, sodass ein verlorenes Paket im Vendor-Bereich den Dashboard-Bereich nicht blockiert.
Von Server Push zu 103 Early Hints
HTTP/2 versuchte, das Entdecken von Ressourcen auf der Serverseite mit Server Push zu lösen: Der Server sollte Ressourcen senden, die der Browser noch nicht angefordert hatte. Das Ziel, Informationen früher bereitzustellen, war richtig, doch die Umsetzung scheiterte. Der Server hatte keine zuverlässige Möglichkeit, festzustellen, ob der Browser bereits eine Ressource im Cache hatte, weshalb er oft Duplikate sendete, Bandbreite verschwendete und um Kapazitäten mit Ressourcen konkurrierte, die der Browser selbst als dringender ansah. Chrome stellte schließlich die Unterstützung für Server Push ein.
Das an seine Stelle getretene Modell verteilt die Verantwortung vernünftiger: Der Server liefert Informationen, und der Browser entscheidet, was und wann heruntergeladen werden soll.
103 Early Hints setzen dies in die Praxis um. Während der Server noch die Hauptantwort zusammenstellt, sendet er einen vorläufigen 103-Status mit Link-Headern. Der Browser kann sofort mit dem Herunterladen dieser Ressourcen beginnen, und bis die endgültige 200 OK-Antwort mit dem HTML eintrifft, könnten einige davon bereits fertig sein.
Browser Server
│ │
│──── GET / ─────────────→ │
│ │ (generating HTML...)
│ ←─── 103 Early Hints ─── │
│ Link: </assets/main.js>; rel=modulepreload
│ Link: </assets/vendor.js>; rel=modulepreload
│ │
│ (fetching chunks now...) │ (still generating...)
│ │
│ ←─── 200 OK + HTML ───── │
│ (chunks already downloading or done)
Zum Zeitpunkt der Erstellung verfügt NGINX ab Version 1.29.0 aus Juni 2025 über eine eingebaute Unterstützung für Early Hints, und Cloudflare stellt sie als Schalter in seiner Konsole zur Verfügung. In Node.js bietet das Antwortobjekt die Methode writeEarlyHints(), die Sie in einem benutzerdefinierten Server oder Middleware vor dem Senden der eigentlichen Antwort aufrufen können:
// In a custom server or middleware
res.writeEarlyHints({
link: [
"</assets/main.js>; rel=modulepreload; as=script",
"</assets/vendor.js>; rel=modulepreload; as=script",
"</assets/Dashboard.js>; rel=modulepreload; as=script",
],
});
// Then proceed with normal response
res.status(200).send(html);
Der Codeausschnitt verwendet eine Express-ähnliche Funktion res.status().send() für die endgültige Antwort. Bei einem reinen Node.js http-Server würden stattdessen res.writeHead() und res.end() verwendet. Early Hints sind nur dann sinnvoll, wenn es tatsächliche Verarbeitungszeiten des Servers gibt, wie beispielsweise bei Datenbankabfragen oder Rendering, währenddessen der Browser sonst untätig warten würde.
Eine von corewebvitals.io veröffentlichte Messung unter Verwendung der Zeitenmessung in Chrome DevTools zeigte, dass das Einbetten einer kritischen CSS-Datei über Early Hints dazu führte, dass das LCP-Element etwa 35 Prozent schneller sichtbar wurde als bei einer herkömmlichen Vorausladung innerhalb des HTML. Bei einer React-Anwendung bedeutet dies, dass der Hauptdatenblock während die Server noch aktiv sind heruntergeladen wird, anstatt erst nach Erhalt des HTML.
Kombination von streaming SSR, Early Hints und HTTP/3
Reacts Server-Rendering mit Streaming fügt ein weiteres Mittel hinzu. Anstatt die Antwort bis zur Vollendung der Seite aufzubewahren, sendet der Server den HTML-Code in Etappen:
HTML shell → Suspense fallback → more HTML → resolved content → hydration
Während des Renderings weiß der Server, welche Suspense-Grenzen gerade gerendert werden sollen und von welchen Datenblöcken sie abhängen. Diese Informationen können bereits vor Beginn des HTML-Streams über Early Hints an den Browser übermittelt werden. Eine vereinfachte Zeitlinie:
0ms ── Browser sends request
── Server starts rendering
1ms ── Server knows Dashboard boundary will render
── Server sends 103 Early Hints: Dashboard.js
── Browser starts fetching Dashboard.js
50ms ── Server streams HTML shell
── Browser starts parsing
120ms── Server streams Dashboard content
── Dashboard.js already downloaded
── Hydration starts immediately
Vergleichen Sie dieselbe Anwendung ohne Early Hints, bei der die Entdeckung des Inhalts auf den Client wartet:
0ms ── Browser sends request
50ms ── Server streams HTML shell
── Browser starts parsing
── Browser discovers <script> tags
── main.js starts downloading
180ms── React executes
── Hits Dashboard lazy boundary
── Dashboard.js request starts (now)
300ms── Dashboard.js downloads
── Hydration starts
HTTP/3 verkürzt jeden Abschnitt in beiden Zeitlinien. Was Early Hints verändern, ist der Zeitpunkt, an dem die Anfrage an das Dashboard beginnt – was zu einer anderen und oft größeren Einsparung führt. Wenn die Darstellung in Ihrem Framework bereits durch andere Primitiven abgedeckt wird, geht der Artikel zu React 19.2s SSR-Primitiven wie Activity und teilweiser Vordarstellung genauer darauf ein, wie Streaming-Grenzen erzeugt werden.
Neue Betrachtung der Chunk-Granularität für multiplexierten Transport
Der seit langem geltende Rat, die Anzahl der HTTP-Anfragen zu minimieren, rührte von der sechsteiligen Verbindungsbeschränkung in HTTP/1.1 sowie davon her, dass das Multiplexing in HTTP/2 nur teilweise parallel über einen einzigen TCP-Stream erfolgt. Mit unabhängigen QUIC-Streams ist die Anzahl der Anfragen nicht mehr der dominierende Faktor wie früher, sodass man aggressiver aufteilen kann, ohne die gleichen Gebühren pro Anfrage tragen zu müssen.
Eine sinnvolle Standardaufteilung sieht wie folgt aus:
- React und ReactDOM: ein eigener, stabiler Vendor-Chunk. Er ändert sich selten und kann daher eine lange Cache-Laufzeit haben.
- Router-Bibliothek: ihr eigener Chunk aus demselben Grund der Stabilität.
- Schwerwiegende Drittanbieter-Bibliotheken wie Diagramm- oder Editor-Tools: ein Chunk pro Bibliothek, damit das Aktualisieren einer die anderen nicht ungültig macht.
React.lazy(), sodass die Navigation nur das lädt, was sie benötigt.Der Vorteil ist die präzise Caching-Steuerung. Die Bearbeitung von Dashboard.tsx sollte den Cache für diesen einen Routen-Chunk auflösen, während der Vendor-Bundle weiterhin im Cache bleibt – und das ist nur mit fein abgestufter Aufteilung möglich. Über HTTP/3 werden die entstehenden 8 bis 15 Chunks über unabhängige Streams geladen, ohne dass es zu HOL-Blockierungen zwischen ihnen kommt.
Vite kümmert sich ohne Konfiguration um den größten Teil davon. In webpack drückt splitChunks.cacheGroups dieselbe Richtlinie aus. Die folgende Konfiguration erstellt einen React-Vendor-Chunk, einen Router-Chunk sowie einen ausschließlich asynchronen Charts-Chunk; die priority-Werte bestimmen, welche Gruppe Vorrang hat, wenn ein Modul mehr als einen Test erfüllt, und chunks: „async“ sorgt dafür, dass der Charts-Code nicht bei der initialen Ladenphase geladen wird:
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: "vendor-react",
chunks: "all",
priority: 40,
},
routerVendor: {
test: /[\\/]node_modules[\\/](react-router|react-router-dom)[\\/]/,
name: "vendor-router",
chunks: "all",
priority: 30,
},
chartsVendor: {
test: /[\\/]node_modules[\\/](recharts|d3)[\\/]/,
name: "vendor-charts",
chunks: "async",
priority: 20,
},
},
},
},
};
Es gibt eine Einschränkung: Sehr kleine Codeblöcke verursachen im Browser zusätzliche Verarbeitungs- und Kompilierungskosten pro Datei. Zudem nutzen nicht alle Besucher HTTP/3 – Unternehmens-Firewalls, die UDP auf Port 443 blockieren, sind weit verbreitet – wodurch diese Nutzer auf HTTP/2 ausweichen müssen, wobei ein Teil der durch die Anzahl der Anfragen entstehenden Kosten wieder auftaucht. Messen Sie vorher, bevor Sie die Aufteilung noch feiner als auf Route-Ebene vornehmen. In der Regel ist eine Einteilung nach Route bzw. nach besonders großen Bibliotheken angemessen; nach Komponenten hingegen meist nicht. Wenn Sie Next.js verwenden, behandelt der Artikel zu Turbopacks Chunking-Steuerungsmöglichkeiten die entsprechenden Einstellungen dort.
Warum das Vorkommen von allem trotzdem kontraproduktiv ist
Multiplexing ermöglicht es vielen Streams, eine Verbindung zu teilen, gewährt ihnen jedoch kein unbegrenztes Bandbreitenlimit und macht sie nicht alle gleich wichtig. Zwanzig modulepreload-Anweisungen teilen sich weiterhin einen einzigen Datenstrom; der Konflikt wird lediglich auf mehrere Streams verteilt.
Die relevante Frage lautet daher nicht „Was könnte vorgegeladen werden?“, sondern „Welchen wichtigen Ressourcen wird der Browser erst zu spät Beachtung schenken?“ Als Ausgangspunkt sollten folgende Punkte in Betracht gezogen werden:
- Kritische Schriftart:
preload. - Kritische Stylesheet-Datei:
preload, oderpreinit, wenn sie vor dem Zeichnen angewendet werden muss. - Kritisches JavaScript-Modul:
preloadModule. - Wichtiger CDN- oder API-Quellort aus einem anderen Ursprung:
preconnect.
preload mit fetchpriority="high".preloadModule, ausgelöst durch die Absicht des Benutzers.prefetch mit niedriger Priorität.Betrachten Sie jeden Punkt als „zu berücksichtigen“, nicht als Regel. Es gibt keine universelle Preload-Liste, nur Ressourcen, die sowohl wichtig sind als auch erst spät in Ihrer spezifischen Anwendung entdeckt werden.
Dieselbe Beschränkung gilt für fetchpriority. Browser priorisieren Ressourcen bereits mithilfe ausgefeilter Heuristiken, und das Markieren aller Ressourcen als high ist gleichbedeutend damit, nichts zu markieren. Überschreiben Sie den Standard nur dann, wenn Messungen zeigen, dass der Browser falsch handelt.
Fünf Fragen zur Überprüfung einer Ladestrategie
Beim Prüfen, wie eine React-Anwendung ihre Ressourcen lädt, gehen Sie nacheinander die folgenden Fragen durch:
- Zu welchem Zeitpunkt wird diese Ressource entdeckt? Wenn die ehrliche Antwort „nach dem Ausführen von JavaScript“ oder „nachdem React gerendert hat“ lautet, gibt es vermutlich Möglichkeiten, sie früher sichtbar zu machen.
- Zu welchem Zeitpunkt benötigt der Benutzer sie? Etwas kann wichtig sein, ohne sofort benötigt zu werden. Dieser Unterschied bestimmt zwischen
preload(jetzt) undprefetch(in der Freizeit).
preconnect, dann preload oder preloadModule, anschließend preinit, dann die 103 Early Hints, und schließlich das streamingbasierte SSR mit Hinweisen, die der Server aus dem zu rendernden Inhalt ableitet.Wie die Schichten zusammenpassen
Von Anfang bis Ende: Das Laden einer modernen React-Anwendung beinhaltet sechs Schichten, von denen jede ihren eigenen Aufgabenbereich hat:
Application intent (React knows which routes and components are needed)
↓
Resource APIs (preconnect / preload / preloadModule / preinit)
↓
Server-side surfacing (103 Early Hints / streaming SSR)
↓
Browser resource scheduler (priority, cache, bandwidth estimation)
↓
HTTP/3 / QUIC transport (independent streams, 0-RTT, connection migration)
↓
Network
Der ältere Ansatz versuchte, Ressourcen so stark wie möglich am Client bereitzustellen: Server Push, Vorausladen aller Inhalte sowie Zusammenfassung der Pakete, um die Anzahl der Anfragen zu reduzieren. Der aktuelle Ansatz besteht darin, dem Browser in jeder Schicht umfangreichere Informationen zur Verfügung zu stellen und ihn die Scheduling-Aufrufe selbst durchführen zu lassen. HTTP/3 bietet einen Transportmechanismus, der effizient auf diese Entscheidungen reagieren kann. Frühe Hinweise übermitteln dem Browser bereits vor Existenz des HTMLs Kenntnisse vom Server. Reacts Ressourcen-APIs ermöglichen es der Anwendung, ihren Wunsch zum Zeitpunkt der Komponentenbereitstellung mitzuteilen.
Keine Schicht kann eine andere ersetzen. HTTP/3 kann nicht das herunterladen, was noch nicht entdeckt wurde. Early Hints sind nutzlos, wenn der Server keine Ahnung hat, welche Routen verwendet werden. Und preloadModule kann einen monolithischen 2-MB-Bundle nicht retten, dessen Kompilierung auf einem Mittelklasse-Telefon 800 ms dauert.
Kernpunkte
Der größte Einfluss von HTTP/3 auf das Vorausladen besteht nicht darin, dass die Übertragungen schneller werden; vielmehr machen schnellere Übertragungen eine späte Entdeckung relativ teurer. Wenn eine 400-ms-Übertragung auf 80 ms verkürzt wird, wird die zuvor darin verborgene Entdeckungsverzögerung zum dominierenden Kostenfaktor, und eine Strategie, die auf das alte Engpassproblem abgestimmt war, optimiert nun etwas Falsches.
- Laden Sie eine Ressource vor, weil der Browser sie sonst zu spät finden würde – und nicht nur, weil es wichtig ist. Wichtige Ressourcen, die rechtzeitig entdeckt werden, benötigen keinen Hinweis, und unwichtige Ressourcen, die spät entdeckt werden, verdienen keinen solchen Hinweis.
Suspenseverbessert das, was der Benutzer während des Wartens sieht; es beschleunigt jedoch nicht das Laden der Datenblöcke.- Wählen Sie den schwächsten funktionierenden Hinweis aus:
preconnectfür Origin-Adressen,preloadfür Dateien,preloadModulefür Module sowiepreinit, wenn etwas angewendet oder ausgeführt werden muss. - Lösen Sie das Vorladen von Routen-Datenblöcken durch Absichtssignale wie Hover oder Focus aus und lassen Sie Early Hints sowie streamingbasiertes SSR das offenlegen, was der Server bereits weiß.
- Trennen Sie die Vorgehensweise nach Routen sowie nach großen Bibliotheken auf, berücksichtigen Sie den HTTP/2-Fallback und messen Sie erst, bevor Sie noch feinere Einstellungen vornehmen.
HTTP/3 hat das Vorkommen von Vorladen nicht überflüssig gemacht. Es hat vielmehr klarer gemacht, wofür Vorladen dient.
Verwandte Artikel
- React 19.2 SSR Primitives: Activity, cacheSignal und PPR erläutert — Erfahren Sie, wie die neuen Komponenten Activity, cacheSignal sowie das Partial Pre-rendering in React 19.2 Entwicklern direkten Einfluss auf die Leistung des Server-Renderings geben.
- Docling-Pipelines über HTTP steuern: Vom Projektsetup bis zu Indexed Chunks — Schauen Sie sich die REST-API der Docling-Pipelines Schritt für Schritt an: Starten des Servers, Erkennen der Operatoren, Validierung und Ausführung eines Ingestion-DAGs sowie Abruf der Ausführungsdaten.