- Die künftige Richtung von Next.js ist spannend
- Es gab zwar Probleme mit Server Actions, aber mit
useOptimistic und useFormStatus aus React 19 scheint eine Verbesserung möglich
- Auch der
useFetcher-Ansatz von Remix bietet eine gute DX
- Besonders hervor stechen PPR (Partial Pre-rendering) von Next.js und das neue granulare Cache-System
- Insgesamt hinterlässt es einen sehr positiven Eindruck
The Big Picture
- Das neue Cache-System kann in
next.config.js experimentell aktiviert werden
- Cache-Profile lassen sich definieren, um unterschiedliche Ablaufzeiten und Revalidierungsintervalle festzulegen
// next.config.js
const config = {
experimental: {
// Neues Caching-System aktivieren. Jetzt kann `use cache` im Code verwendet werden
dynamicIO: true,
// Optional: Cache-Profile konfigurieren
cacheLife: {
blog: {
stale: 3600, // Client-Cache beibehalten: 1 Stunde
revalidate: 900, // Auf dem Server aktualisieren: 15 Minuten
expire: 86400, // Maximale Lebensdauer: 1 Tag
},
},
},
};
Grundlegende Verwendung von use cache
- Caching ist auf Datei-, Komponenten- und Funktionsebene über die Deklaration
"use cache" möglich
- Im Codebeispiel lässt sich Caching einfach anwenden, indem
use cache hinzugefügt wird
- Mit
cacheTag, revalidateTag usw. kann der Cache zum gewünschten Zeitpunkt invalidiert werden
// 1. Caching auf Dateiebene
"use cache";
export default function Page() {
return <div>Cached Page</div>;
}
// 2. Caching auf Komponentenebene
export async function PriceDisplay() {
"use cache";
const price = await fetchPrice();
return <div>${price}</div>;
}
// 3. Caching auf Funktionsebene
export async function getData() {
"use cache";
return await db.query();
}
Tag-basiertes Caching
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache';
// Bestimmte Datengruppe cachen
export async function ProductList() {
'use cache';
cacheTag('products');
const products = await fetchProducts();
return <div>{products}</div>;
}
// Cache bei Datenänderungen invalidieren
export async function addProduct() {
'use server';
await db.products.add(...);
revalidateTag('products');
}
Benutzerdefinierte Cache-Profile
- Mit
unstable_cacheLife können die in next.config.js definierten Cache-Profile geladen werden
- Im Code wird der deklarierte Profilname (z. B.
"blog") verwendet, um die Cache-Richtlinie anzuwenden
import { unstable_cacheLife as cacheLife } from "next/cache";
export async function BlogPosts() {
"use cache";
cacheLife("blog"); // Vordefiniertes Cache-Profil für den Blog verwenden
return await fetchPosts();
}
Wichtige, aber leicht zu übersehende Punkte
Automatische Generierung von Cache-Keys
props und arguments einer Komponente werden automatisch in den Cache-Key aufgenommen
- Nicht serialisierbare Werte (z. B. Funktionen) werden als „unveränderliche Referenz“ behandelt
export async function UserCard({ id, onDelete }) {
"use cache";
// id wird in den Cache-Key aufgenommen
// onDelete wird übergeben, beeinflusst das Caching aber nicht
const user = await fetchUser(id);
return <div onClick={onDelete}>{user.name}</div>;
}
Mischung aus dynamischen und gecachten Inhalten
- Dynamische Inhalte können als Child innerhalb gecachter Inhalte übergeben und gemischt verwendet werden
- Durch Angabe eines
cacheTag-Arrays können mehrere Tags gleichzeitig angewendet und invalidiert werden
export async function CachedWrapper({ children }) {
"use cache";
const header = await fetchHeader();
return (
<div>
<h1>{header}</h1>
{children} {/* Dynamische Inhalte bleiben unverändert */}
</div>
);
}
export async function ProductPage({ id }) {
"use cache";
cacheTag(["products", `product-${id}`, "featured"]);
// Die Invalidierung ist über jeden dieser Tags möglich
}
Caching-Hierarchie
- Wenn
"use cache" auf der obersten Ebene deklariert wird, wird der gesamte Bereich gecacht
- Bestimmte Teile (z. B. dynamische Abschnitte mit Suspense) können aus dem Caching-Bereich ausgenommen werden
"use cache";
export default async function Page() {
return (
<div>
<CachedHeader />
<div>
<Suspense fallback={<Loading />}>
<DynamicFeed /> {/* Dynamischer Inhalt */}
</Suspense>
</div>
</div>
);
}
Typsicherheit
- Strings wie Cache-Keys und Cache-Profile können als Konstanten verwaltet werden, um Magic Strings zu reduzieren
- Praktisch ist ein Muster zur Tag-Erzeugung, ähnlich wie bei React Query
// Cache-Profilschlüssel als Konstanten verwalten
export const CACHE_LIFE_KEYS = {
blog: "blog",
} as const;
const config = {
experimental: {
cacheLife: {
[CACHE_LIFE_KEYS.blog]: {
stale: 3600,
revalidate: 900,
expire: 86400,
},
},
},
};
So verwaltet man Cache-Tags effizient
- Anwendung eines Tag-Factory-Musters im Stil von React Query
export const CACHE_TAGS = {
blog: {
all: ["blog"] as const,
list: () => [...CACHE_TAGS.blog.all, "list"] as const,
post: (id: string) => [...CACHE_TAGS.blog.all, "post", id] as const,
comments: (postId: string) =>
[...CACHE_TAGS.blog.all, "post", postId, "comments"] as const,
},
} as const;
// Caching-Tags setzen
function tagCache(tags: string[]) {
cacheTag(...tags);
}
// Verwendungsbeispiel
export async function BlogList() {
"use cache";
tagCache(CACHE_TAGS.blog.list());
}
3 Kommentare
Ich denke, es ist sinnvoll, ein Framework wie Next.js oder Remix nur dann zu verwenden, wenn SSR wegen der Bedeutung von SEO wirklich erforderlich ist.
Gerade bei B2B-Business-Produkten oder Services wie Backoffice-Anwendungen, bei denen SEO nicht wichtig ist, sollte man die Einführung von Next.js meiner Meinung nach sorgfältig abwägen. Denn die von Next.js vorgegebenen Interfaces und die zusätzliche Komplexität können die Entwicklungsproduktivität senken.
Persönlich finde ich, dass in Fällen, in denen SEO nicht notwendig ist, Vite + React in Bezug auf Entwicklungsproduktivität und Flexibilität deutlich besser ist.
Next.js ist seit Version 13 wirklich brauchbar geworden, und in letzter Zeit gefällt es mir sogar richtig gut. Es scheint de facto zum Standard für den Full-Stack-Webentwicklungs-Stack zu werden.