EMZETT.
Login

React.cache()

Kurz: Eine React-Funktion, die das Ergebnis eines asynchronen Aufrufs (z. B. einer Datenbank-Abfrage) innerhalb eines einzelnen Requests zwischenspeichert — ruft man dieselbe Funktion mit denselben Argumenten mehrfach im selben Request auf, läuft die eigentliche Arbeit nur einmal.

Genauer: Wichtig: Der Cache gilt nur für die Dauer eines einzelnen Server-Requests, nicht zwischen verschiedenen Usern oder Requests (kein globaler/geteilter Cache). Typischer Anwendungsfall: Eine Funktion wie getCurrentUser() wird in einem Seitenaufruf oft an mehreren, unabhängigen Stellen aufgerufen (z. B. einmal im Header, einmal in der Seite selbst) — ohne React.cache() bedeutet das mehrere identische Datenbank-Roundtrips pro einzelnem Seitenaufruf.

Kontext bei uns: War die plausible Hauptursache dafür, dass der Vercel-Server bei Emzett gelegentlich abstürzte, besonders bei mehreren gleichzeitigen Besuchern — getCurrentUser()/getSetting() liefen auf 11 von 12 Seiten doppelt, jeweils mit vollem DB-Roundtrip. Mit React.cache() umschlossen: aus zwei Aufrufen wurde live verifiziert ein einziger echter DB-Zugriff.

Im Detail

Die Funktionsweise dahinter: React.cache() merkt sich pro Request (technisch: pro React-Server-Render-Durchlauf) welche Funktion mit welchen Argumenten bereits aufgerufen wurde, und gibt beim zweiten identischen Aufruf einfach das bereits berechnete Ergebnis zurück, statt die Funktion erneut auszuführen:

import { cache } from "react";
 
export const getCurrentUser = cache(async () => {
  const session = await getSession();
  if (!session) return null;
  return db.query.users.findFirst({ where: eq(users.id, session.userId) });
});

Wichtig ist die Abgrenzung zu anderen Next.js-Caching-Mechanismen, die ähnlich klingen, aber etwas anderes tun: fetch()-Caching und Cache Tags/revalidateTag cachen Daten SEITENÜBERGREIFEND und ZWISCHEN Requests (bis sie explizit invalidiert werden), während React.cache() ausdrücklich NUR innerhalb eines einzelnen Request-Lebenszyklus dedupliziert und danach verworfen wird — für den nächsten Nutzer bzw. den nächsten Seitenaufruf läuft die Funktion wieder ganz normal.

Warum die Dedupe nicht über Datenungleichheit hinweg funktioniert

React.cache() erkennt “gleiche Aufrufe” über einen Vergleich der übergebenen Argumente (ähnlich einem Memoisierungs-Schlüssel). Ruft man dieselbe Funktion mit UNTERSCHIEDLICHEN Argumenten auf, greift die Dedupe nicht — jeder eindeutige Argumentsatz läuft einmal pro Request:

await getUserById("abc"); // läuft echt
await getUserById("abc"); // aus Cache, kein DB-Zugriff
await getUserById("xyz"); // andere Argumente -> läuft wieder echt

Das ist meist genau das gewünschte Verhalten, aber ein häufiger Denkfehler: Wer annimmt, React.cache() würde JEDEN Datenbank-Zugriff im Request bündeln, wird überrascht, wenn zwei leicht unterschiedliche Aufrufe (z. B. einmal mit, einmal ohne optionalem Parameter) trotzdem zwei echte Abfragen auslösen.

Abgrenzung zu useMemo

useMemo ist clientseitig (läuft im Browser, pro Komponente, über Re-Renders hinweg) und für synchrone Berechnungen gedacht — React.cache() ist server-only, für asynchrone Datenzugriffe gemacht und dedupliziert über die gesamte Komponentenbaum-Breite eines Requests hinweg, nicht nur innerhalb einer einzelnen Komponente. Beide lösen unterschiedliche Probleme und lassen sich nicht gegeneinander austauschen.

Siehe auch: Neon, Neon Auto-Suspend, Cache Tags/revalidateTag