Wo soll die Server-Client-Grenze in einer Next.js App Router-Seite gezogen werden?
Ein praktisches mentales Modell für React Server Components: Was wo ausgeführt wird, wie eine Blogartikel-Seite in Server- und Client-Bereiche aufgeteilt wird, sowie die einzuhaltenden Importregeln.
React Server Components wirken oft rätselhaft, selbst nachdem man die Dokumentation gelesen hat – hauptsächlich deshalb, weil sie von einem Annahmen aufgeben verlangen, die jeder React-Entwickler seit Jahren hatte: dass Komponenten im Browser ausgeführt werden. Sobald diese Annahme fällt, folgt der Rest recht natürlich. Dieser Artikel entwickelt ein mentales Modell anhand einer einzelnen Blog-Post-Seite und zeigt, welche Teile auf dem Server liegen sollten, welche den Client benötigen, sowie die wenigen Regeln, die die Grenze zwischen ihnen klar halten.
Für einen tieferen Einblick in den Rendering-Prozess selbst siehe die Architektur hinter dem zero-Bundle-Rendering mit Server Components.
Von „Alles wird hydratisiert“ zu „Nur das, was nötig ist“
Vor Server Components wurde jede React-Komponente letztendlich im Browser ausgeführt. Selbst bei serverseitiger Renderung wurde die JavaScript-Codebasis für den gesamten Baum an den Client gesendet und dort initialisiert, damit die Anwendung interaktiv werden konnte. Das ist verschwenderisch für Komponenten, die lediglich Daten abrufen und diese als Props weitergeben – ihr Code wird heruntergeladen und ausgeführt, ohne jemals auf den Benutzer zu reagieren.
Server Components ändern das, indem sie ausschließlich auf dem Server ausgeführt werden. Ihr Code wird niemals an den Browser gesendet und auch nicht initialisiert, wodurch kein JavaScript in den Client-Bundle aufgenommen wird. Was beim Browser ankommt, ist die bereits renderierte Ausgabe, die React zusammen mit dem HTML in einen kompakten Datensatz serialisiert.
Die einfachste Art, sich diese beiden Arten zu merken:
- Client Component: wird im Browser ausgeführt (nach vorheriger Vorenderung auf dem Server), kann Zustand speichern und auf Ereignisse reagieren.
Warum die Unterscheidung vorteilhaft ist
Nehmen wir eine typische Blog-Beitragseite. Der Titel und der Text stammen aus einer Datenbank, sehen für jeden Besucher gleich aus und berücksichtigen Klicks nicht. Traditionell gelangt der Code, der sie darstellt, zusammen mit allen Markdown- oder Formatierungsbibliotheken dennoch zum Browser. Mit Serverkomponenten bleibt all das auf dem Server. Dadurch wird die Dateigröße verringert und die Seiten laden in der Regel schneller, insbesondere auf langsameren Geräten.
Teams, die zum App Router wechseln, der auf Serverkomponenten basiert, berichten in einigen Fällen von besseren Core Web Vitals-Werten, insbesondere beim LCP. Betrachten Sie dies als kontextabhängig: Der Vorteil hängt davon ab, wie viel Client-JavaScript Ihre Seiten weglassen können – messen Sie daher Ihre eigenen Routen.
Eine Blog-Beitragseite, in zwei Teile geteilt
In einem Next.js App Router-Projekt ist eine Seite-Datei standardmäßig ein Server Component, es sei denn, es steht etwas anderes darauf. Die folgende Seite ist eine async-Funktion, die direkt von der Datenschicht auf den Post wartet, den Fall „nicht gefunden“ handhabt, den statischen Inhalt rendernt und anschließend ein einzelnes interaktives Kindelement für Kommentare einfügt. Sie ist nach der Next.js 14 API geschrieben:
// app/posts/[slug]/page.tsx
// This is a Server Component by default — no "use client" needed
import { getPostBySlug } from "@/lib/db"
import { PostContent } from "@/components/PostContent"
import { CommentSection } from "@/components/CommentSection"
type Props = {
params: { slug: string }
}
export default async function PostPage({ params }: Props) {
// Direct DB call — no useEffect, no API route, no loading state
const post = await getPostBySlug(params.slug)
if (!post) {
return <div>Post not found.</div>
}
return (
<article className="max-w-2xl mx-auto py-12 px-4">
<h1 className="text-3xl font-bold mb-4">{post.title}</h1>
<PostContent content={post.body} />
{/* This one needs interactivity — so it's a Client Component */}
<CommentSection postId={post.id} />
</article>
)
}
Beachten Sie, was fehlt: kein useState, kein useEffect, keine API-Route-Umhüllung und keine Verwaltung eines Ladezustands. Die Daten werden genauso wie in jeder anderen async-Funktion abgewartet, was der Kern dieses Modells ist.
Zwei praktische Hinweise. Ab Next.js 15 wird params als Promise übergeben, sodass die Seite zuerst await params ausführen muss, bevor sie slug liest; überprüfen Sie daher die von Ihnen verwendete Version. Bei einem echten „Not Found“-Fall gibt das Aufrufen von notFound() aus next/navigation einen korrekten 404-Status zurück, anstatt eine normale Seite mit einer Fehlermeldung.
Das Kommentarfeld ist anders. Es speichert den Entwurftext im Zustand und reagiert auf Eingaben sowie Klicks, weshalb es im Browser ausgeführt werden muss. Die Direktive "use client" am Anfang der Datei kennzeichnet es als Client-Komponente:
// components/CommentSection.tsx
"use client" // opts into browser rendering
import { useState } from "react"
type Props = {
postId: string
}
export function CommentSection({ postId }: Props) {
const [comment, setComment] = useState("")
const handleSubmit = async () => {
await fetch("/api/comments", {
method: "POST",
body: JSON.stringify({ postId, comment }),
})
setComment("")
}
return (
<div className="mt-8">
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Leave a comment..."
className="w-full border rounded p-2 text-sm"
/>
<button
onClick={handleSubmit}
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded text-sm"
>
Post Comment
</button>
</div>
)
}
Alles, was interaktiv ist, befindet sich hier: der lokale Zustand für das Textfeld, ein Handler für Änderungen sowie ein Submit-Handler, der eine Anfrage an einen API-Pfad sendet und anschließend das Feld leert. Bei der tatsächlichen Implementierung sollte man im Anfrageheader Content-Type: application/json angeben und eine Server Action als Alternative zu einem separaten API-Pfad in Betracht ziehen.
Durch diese Trennung wird die Struktur leicht verständlich: Der Server verwaltet die Daten, der Client kümmert sich um die Interaktion.
Regeln zur Aufrechterhaltung einer klaren Trennung
- Im App Router sind Komponenten standardmäßig Server Components.
- Fügen Sie
"use client"nur dort hinzu, wo Sie Zustand, Effekte, Event-Handler oder Browser-APIs wiewindowundlocalStoragebenötigen. - Server Components können Client Components importieren und rendern, wie es die Seite mit
CommentSectiontut.
children oder andere Props erhalten, was es ermöglicht, serverseitig gerenderten Inhalt innerhalb einer interaktiven Schale zu verschieben."use client" markiert außerdem eine Grenze und nicht nur eine einzelne Datei: Alles, was von dieser Datei importiert wird, gehört zum Client-Bundle. Die Anweisung so tief wie möglich im Baum, auf den kleinen interaktiven Knoten, zu platzieren, hält den Rest auf dem Server.
Zusammenfassung
Das mentale Modell funktioniert sofort – „Wo wird das ausgeführt?“ ist die erste Frage, die man sich zu einer Komponente stellt, und die Standardantwort des App Routers ist der Server.
- Server Components verlagern die Darstellung und den Datenzugriff auf den Server und liefern kein Component-JavaScript mit.
- Der Datenzugriff erfolgt innerhalb des Components einfach über
async/await. - Betrachten Sie
"use client"als Option für interaktive Komponenten, nicht als Standard. - Ein praktischer erster Schritt: Wählen Sie eine Datenerfassungskomponente in einem bestehenden Projekt aus, prüfen Sie, ob sie tatsächlich den Browser benötigt, und konvertieren Sie sie ggf.
Sobald dieses Modell etabliert ist, werden Layouts, Suspense-Streaming, Server Actions sowie parallele Routen viel leichter zu erlernen, da alles auf derselben serverbasierten Grundlage aufbaut.
Verwandte Literatur
- Erstellung einer widerstandsfähigen Film-Detailseite mit Next.js App Router — Lernen Sie, wie man Daten der OMDB API in Next.js App Router mithilfe von asynchronen Server Components, awaited Parametern sowie einer korrekten 404-Behandlung abruft und speichert.
- Server Actions oder Route Handlers? Ein Entscheidungshilfeleitfaden für Next.js 16 — Erfahren Sie, wann eine Mutation in Next.js 16 in einen Server Action gehört und wann ein Route Handler erforderlich ist, mit korrigierten Beispielen für Formulare, Fehler, Optimismus-Prinzip und Webhooks.