perf: Firmendaten und Social-Links je Request nur noch einmal laden

getCompanyInfo() wurde bei JEDEM Seitenaufruf mehrfach ausgefuehrt: das
Root-Layout braucht es zweimal (einmal in generateMetadata, einmal fuer
das LocalBusiness-Schema im Rumpf), dazu kommt fast jede Seite mit einem
eigenen Aufruf. Jeder davon war eine eigene Datenbankabfrage ueber
dieselben zehn Schluessel — dreimal dasselbe Ergebnis pro gerenderter
Seite, auf jeder Seite des Shops.

Beide Funktionen sind jetzt in React-cache() gehuellt. Die
Deduplizierung gilt je Request und ist KEIN serverweiter
Zwischenspeicher: eine Aenderung im Admin-Bereich wirkt unveraendert
sofort, es gibt nichts zu invalidieren. Beide Funktionen nehmen keine
Argumente, damit greift die Deduplizierung zuverlaessig — cache()
unterscheidet Aufrufe anhand ihrer Argumente.

Geprueft: "next build" laeuft durch, tsc 0 Fehler, vitest 328/328 gruen,
eslint 0 Warnungen.

Nicht geaendert, aber geprueft: die Admin-Listen ohne "take" (Chargen,
Ernten, Gutscheine, Inhalte, Produkte, Versandstaffeln) betreffen
ausnahmslos kleine, langsam wachsende Tabellen — eine Begrenzung waere
dort Vorratsarbeit. Die Listen auf den wachsenden Tabellen (Bestellungen,
Bewertungen, Audit-Log, Retouren, Anfragen, Benutzer) sind bereits
begrenzt.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5 committed 2026-09-19 04:47:43 +02:00
1 parent 2bbb38c31a
commit e5cae9b49d
2 files changed
+23 -4

No files matched your search

+16 -2
View File
@@ -1,3 +1,4 @@
import { cache } from "react";
import { siteConfig } from "@/lib/site-config"; import { siteConfig } from "@/lib/site-config";
import { getContentValues } from "@/lib/content"; import { getContentValues } from "@/lib/content";
@@ -20,8 +21,21 @@ const COMPANY_KEYS = [
* Konsumenten nur `siteConfig` gegen `await getCompanyInfo()` tauschen * Konsumenten nur `siteConfig` gegen `await getCompanyInfo()` tauschen
* muessen. domain/country bleiben aus siteConfig (keine Content-Felder, * muessen. domain/country bleiben aus siteConfig (keine Content-Felder,
* echte Infra-Werte). * echte Infra-Werte).
*
* In React-cache() gehuellt, weil diese Funktion je Seitenaufruf MEHRFACH
* aufgerufen wird: allein das Root-Layout braucht sie zweimal (einmal fuer
* generateMetadata, einmal fuer das LocalBusiness-Schema im Rumpf), dazu
* kommt fast jede Seite mit einem eigenen Aufruf. Ohne die Huelle war das je
* Aufruf eine eigene Datenbankabfrage ueber dieselben zehn Schluessel —
* dreimal dasselbe Ergebnis pro gerenderter Seite.
*
* cache() ist auf EINEN Request begrenzt und kein serverweiter Zwischen-
* speicher: eine Aenderung im Admin-Bereich wirkt unveraendert sofort, es
* gibt nichts zu invalidieren. Die Funktion nimmt keine Argumente, damit
* greift die Deduplizierung zuverlaessig (cache() unterscheidet Aufrufe
* anhand der Argumente).
*/ */
export async function getCompanyInfo() { export const getCompanyInfo = cache(async () => {
// Firmendaten sind nicht lokalisiert (siehe content-registry.ts) — die // Firmendaten sind nicht lokalisiert (siehe content-registry.ts) — die
// uebergebene Locale wirkt sich fuer diese Schluessel nicht aus. // uebergebene Locale wirkt sich fuer diese Schluessel nicht aus.
const v = await getContentValues([...COMPANY_KEYS], "de"); const v = await getContentValues([...COMPANY_KEYS], "de");
@@ -41,4 +55,4 @@ export async function getCompanyInfo() {
city: v["company.city"], city: v["company.city"],
}, },
}; };
} });
+7 -2
View File
@@ -1,3 +1,4 @@
import { cache } from "react";
import { getContentValues } from "@/lib/content"; import { getContentValues } from "@/lib/content";
const SOCIAL_KEYS = ["social.tiktok", "social.youtube", "social.instagram"] as const; const SOCIAL_KEYS = ["social.tiktok", "social.youtube", "social.instagram"] as const;
@@ -13,12 +14,16 @@ export interface SocialLinks {
* Media") — nicht lokalisiert, wie Firmendaten (company-info.ts). Ein leerer * Media") — nicht lokalisiert, wie Firmendaten (company-info.ts). Ein leerer
* String bedeutet "kein Profil hinterlegt"; der Footer rendert dann kein * String bedeutet "kein Profil hinterlegt"; der Footer rendert dann kein
* Icon fuer diese Plattform (siehe components/site-footer.tsx Footer()). * Icon fuer diese Plattform (siehe components/site-footer.tsx Footer()).
*
* In React-cache() gehuellt, aus demselben Grund wie getCompanyInfo: die
* Deduplizierung gilt je Request, nicht serverweit — Aenderungen im
* Admin-Bereich wirken unveraendert sofort.
*/ */
export async function getSocialLinks(): Promise<SocialLinks> { export const getSocialLinks = cache(async (): Promise<SocialLinks> => {
const v = await getContentValues([...SOCIAL_KEYS], "de"); const v = await getContentValues([...SOCIAL_KEYS], "de");
return { return {
tiktok: v["social.tiktok"], tiktok: v["social.tiktok"],
youtube: v["social.youtube"], youtube: v["social.youtube"],
instagram: v["social.instagram"], instagram: v["social.instagram"],
}; };
} });