Stolpersteine
Die Fehler, die in fast jedem Next.js-Projekt einmal passieren
Acht Muster, die immer wieder auftauchen – jeweils mit dem Denkfehler dahinter und der kürzesten Reparatur.
Wenn du die vorigen Lektionen gelesen hast, kennst du die Bausteine. Diese hier sammelt die Fälle, in denen sie im Alltag falsch zusammengesetzt werden.
1. „use client" ganz oben in der Seite
"use client";
export default function Seite() {
const [offen, setOffen] = useState(false);
return (
<>
<RiesigeListe />
<button onClick={() => setOffen(true)}>Filter</button>
</>
);
} export default async function Seite() {
const daten = await laden();
return (
<>
<RiesigeListe daten={daten} />
<FilterKnopf /> {/* nur diese Datei ist Client */}
</>
);
} Warum es passiert: Die Fehlermeldung zeigt auf die Seite, also setzt man die Zeile dort. Richtig ist, den interaktiven Teil herauszulösen.
2. Die eigene API aus dem eigenen Server aufrufen
// in einer Server-Komponente
const res = await fetch("https://meine-seite.de/api/artikel");
const artikel = await res.json(); // in einer Server-Komponente
const artikel = await db.artikel.findMany(); Ein Netzwerkumweg zu sich selbst, plus Serialisierung nach JSON und zurück. Route HandlerEine `route.ts`, die auf eine Adresse antwortet, ohne eine Seite zu sein – der Weg zu einer eigenen API.→ sind für Zugriffe von außen da.
3. Caching-Fragen im Entwicklungsmodus untersuchen
Dort ist fast nichts zwischengespeichert. Wer wissen will, ob eine Seite statisch wird, braucht npm run build && npm run start – und die Symbolspalte der Bauausgabe.
4. params direkt benutzen
export default function Seite({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>;
} export default async function Seite({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>{slug}</h1>;
} Gilt genauso für searchParams, cookies() und headers(). Ältere Beispiele im Netz zeigen noch den direkten Zugriff.
5. Server Action ohne Berechtigungsprüfung
"use server";
export async function loeschen(id: string) {
await db.artikel.delete({ where: { id } });
} "use server";
export async function loeschen(id: string) {
const nutzer = await aktuellerNutzer();
if (!nutzer?.istAdmin) throw new Error("Nicht erlaubt");
await db.artikel.delete({ where: { id } });
} Für jede Action entsteht ein öffentlicher Endpunkt. Dass der Knopf nur Administratoren angezeigt wird, schützt nichts.
6. Nach dem Schreiben nicht revalidieren
"use server";
import { revalidatePath } from "next/cache";
export async function anlegen(formData: FormData) {
await db.artikel.create({ data: … });
revalidatePath("/blog"); // die Übersicht
revalidatePath("/"); // falls dort die neuesten stehen
} 7. Alles in einen Suspense-Bereich packen
<Suspense fallback={<Spinner />}>
<Artikel />
<Kommentare /> {/* nur der hier ist langsam */}
<Empfehlungen />
</Suspense> <Artikel />
<Suspense fallback={<KommentarSkelett />}>
<Kommentare />
</Suspense>
<Empfehlungen /> Sonst wartet der schnelle Inhalt auf den langsamen – genau das, was Streaming verhindern sollte.
8. useEffect für Daten, die es auf dem Server gibt
"use client";
function Liste() {
const [daten, setDaten] = useState([]);
useEffect(() => {
fetch("/api/artikel").then(r => r.json()).then(setDaten);
}, []);
…
} export default async function Liste() {
const daten = await db.artikel.findMany();
…
} Der zweite Weg spart die API, den Ladezustand, das Wettrennen – und der Inhalt steht sofort im HTML.
▸ Genauer erklärt: eine Checkliste für die Fehlersuche optional
Seite zeigt alte Daten
1. npm run build – steht dort ○ statt ƒ?
2. Harter Reload: frisch? → Router Cache im Browser
3. Nach neuem Build frisch? → revalidate fehlt
4. Immer noch alt? → Data Cache, revalidateTag prüfen
"needs useState" beim Bauen
→ Hook in einer Server-Komponente.
Interaktiven Teil herauslösen, nicht die Seite markieren.
Bundle plötzlich viel größer
→ Client-Grenze ist nach oben gerutscht.
In der Bauausgabe "First Load JS" vergleichen.
Server Action tut nichts
→ Fehlt revalidatePath? Oder wurde geworfen statt
einen Fehlerwert zurückzugeben? Die meisten dieser Fälle sieht man in der Tabelle von npm run build, bevor man auch nur eine Datei öffnet. Ein kurzer Blick darauf bei jeder größeren Änderung erspart lange Suchen.
Sitzt das schon?
6 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.