Die Frontend-Performance richtig hinzubekommen, kann schwierig sein. Selbst in hochoptimierten Apps sind Client-Server-Wasserfälle bei weitem die häufigste Ursache für Probleme. Als wir den Next.js App Router einführten, wollten wir dieses Problem unbedingt lösen. Dazu mussten wir Client-Server-REST-Abfragen mit React Server Components in einem einzigen Roundtrip auf den Server verlagern. Das bedeutete, dass der Server manchmal dynamisch sein musste, was die hervorragende initiale Ladeleistung von Jamstack opferte. Wir entwickelten Partial Prerendering, um diesen Kompromiss zu lösen und das Beste aus beiden Welten zu erhalten.
Allerdings litt die Developer Experience unter den von uns bereitgestellten Caching-Standards und -Steuerungen. Die Standardeinstellung für fetch()
wurde geändert, um die Performance durch standardmäßiges Caching zu begünstigen, aber schnelles Prototyping und hochdynamische Apps litten darunter. Wir boten nicht genug Kontrolle über lokale Datenbankzugriffe, die fetch()
nicht verwendeten. Wir hatten unstable_cache()
, aber es war nicht ergonomisch. Dies führte zur Notwendigkeit von Segment-level-Konfigurationen wie export const dynamic, runtime, fetchCache, dynamicParams, revalidate = ...
als Notlösung.
Natürlich werden wir dies weiterhin für die Abwärtskompatibilität unterstützen. Aber für einen Moment möchte ich, dass Sie all das vergessen. Wir glauben, dass wir eine Idee für etwas Einfacheres haben.
Wir haben an einem neuen experimentellen Modus gearbeitet, das auf nur zwei Konzepten aufbaut: <Suspense>
und use cache
.
Wählen Sie Ihr Abenteuer
Das erste, was Ihnen auffallen wird, ist, dass Sie jetzt einen Fehler erhalten, wenn Sie Daten zu Ihren Komponenten hinzufügen.
Um Daten, Cookies, Header, die aktuelle Zeit oder Zufallswerte zu verwenden, haben Sie jetzt eine Wahl: Möchten Sie, dass die Daten gecacht werden (serverseitig oder clientseitig) oder bei jeder Anfrage neu ausgeführt werden? Ich verwende fetch()
als Beispiel, aber dies gilt für jede asynchrone Node-API, wie Datenbanken oder Timer.
Dynamisch
Wenn Sie noch iterieren oder ein hochdynamisches Dashboard erstellen, können Sie die Komponente in eine <Suspense>
-Grenze einwickeln. <Suspense>
entscheidet sich für dynamisches Datenfetching und Streaming.
Sie können dies auch in Ihrem Root-Layout tun oder loading.tsx
verwenden.
Dadurch bleibt die Shell Ihrer App sofort verfügbar. Sie können weiterhin Daten zu Ihrer Seite hinzufügen, wissend, dass alles standardmäßig dynamisch sein wird. Nichts wird standardmäßig gecacht. Keine versteckten Caches mehr.
Statisch
Wenn Sie etwas Statisches erstellen und keine dynamischen Funktionen verwenden möchten, können Sie die neue use cache
-Direktive verwenden.
Indem Sie die Seite mit use cache
markieren, geben Sie an, dass das gesamte Segment gecacht werden soll. Das bedeutet, dass alle abgerufenen Daten jetzt gecacht werden können, was eine statische Rendering der Seite ermöglicht. Für statische Inhalte wird keine <Suspense>
-Grenze verwendet. Sie können weitere Daten zur Seite hinzufügen, und alles wird gecacht.
Partiell
Sie können auch mischen und kombinieren. Zum Beispiel können Sie use cache
in Ihr Root-Layout einfügen, um sicherzustellen, dass es gecacht wird. Jedes Layout oder jede Seite kann unabhängig gecacht werden.
Während Sie innerhalb einer bestimmten Seite dynamische Daten verwenden:
Gecachte Funktionen
Bei einem hybriden Ansatz wie diesem könnte es bequemer sein, das Caching näher an den API-Aufrufen hinzuzufügen.
Sie können use cache
zu jeder asynchronen Funktion hinzufügen, genau wie use server
. Stellen Sie es sich als eine Server-Aktion vor, aber anstatt einen Server aufzurufen, rufen Sie einen Cache auf. Es unterstützt die gleichen reichhaltigen Typen von Argumenten und Rückgabewerten über JSON hinaus. Der Cache-Key schließt automatisch alle Argumente und Closures ein, sodass Sie keinen Cache-Key manuell angeben müssen.
Da in diesem Layout keine anderen Daten verwendet wurden, kann es statisch bleiben. Ein Vorteil dieses Ansatzes ist, dass Sie, wenn Sie versehentlich neue dynamische Daten zum Layout hinzufügen, während des Builds einen Fehler auslösen, der Sie zwingt, eine neue Entscheidung zu treffen. Wenn Sie use cache
zum gesamten Layout hinzufügen, wird es ohne Fehler gecacht. Welchen Ansatz Sie wählen, hängt von Ihrem Anwendungsfall ab.
Tagging eines Caches
Wenn Sie einen Cache-Eintrag explizit durch ein Tag löschen möchten, können Sie die neue cacheTag()
-API innerhalb der use cache
-Funktion verwenden.
Dann rufen Sie einfach wie zuvor revalidateTag('my-tag')
von einer Server-Aktion auf.
Da diese API nach dem Datenladen aufgerufen werden kann, können Sie jetzt Daten verwenden, um Ihre Cache-Einträge zu taggen.
Definieren der Lebensdauer eines Caches
Wenn Sie steuern möchten, wie lange ein bestimmter Eintrag oder eine Seite im Cache leben soll, können Sie die cacheLife()
-API verwenden:
Standardmäßig akzeptiert es die folgenden Werte:
"seconds"
"minutes"
"hours"
"days"
"weeks"
"max"
Wählen Sie einen groben Bereich, der am besten zu Ihrem Anwendungsfall passt. Sie müssen keine genaue Zahl angeben und berechnen, wie viele Sekunden (oder waren es Millisekunden?) in einer Woche sind. Sie können jedoch auch spezifische Werte angeben oder eigene benannte Cache-Profile konfigurieren.
Zusätzlich zu revalidate
kann diese API die stale
-Zeit des Client-Caches sowie expire
steuern, das festlegt, wann eine Seite abläuft, wenn sie eine Weile nicht viel Traffic hatte.
Experimentell
Dies ist immer noch ein sehr experimentelles Projekt. Es ist noch nicht produktionsreif und hat noch fehlende Funktionen und Bugs. Insbesondere wissen wir, dass wir die Fehlerstapel für diesen neuen Fehlertyp verbessern müssen. Wenn Sie jedoch abenteuerlustig sind, würden wir uns über Ihr frühes Feedback freuen.
Wir werden einen detaillierteren Upgrade-Pfad veröffentlichen. Abgesehen von den frühen Fehlern ist die wichtigste Breaking Change hier die Rücknahme des standardmäßigen Cachings von fetch()
. Dennoch empfehlen wir, in diesem frühen experimentellen Stadium nur mit neuen Projekten zu experimentieren. Wenn es sich bewährt, hoffen wir, eine Opt-in-Version in einem Minor-Release zu veröffentlichen und es in einem zukünftigen Major-Release zum Standard zu machen.
Um damit zu experimentieren, müssen Sie sich auf der canary
-Version von Next.js befinden:
Sie müssen auch das experimentelle dynamicIO-Flag in next.config.ts
aktivieren:
Lesen Sie mehr über use cache
, cacheLife
und cacheTag
in unserer Dokumentation.