Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Beim ersten Öffnen der Next.js-Dokumentation war ich ratlos. In der linken Sidebar standen „Pages Router” und „App Router” nebeneinander – quasi: „Nehmen Sie, was Sie wollen.” Aber welches? Was ist der Unterschied? Die Docs gaben keine klare Antwort, und je länger ich las, desto unübersichtlicher wurde es. In Tutorials tauchten mal der pages-, mal der app-Ordner auf – und der Code sah völlig anders aus.
Später wurde mir klar: Next.js hat tatsächlich zwei völlig getrennte Routing-Systeme. Das ältere heißt Pages Router – stabil und bewährt, aber ohne manche neuen Features. Das neuere ist der App Router, seit v13 eingeführt, ab v13.4 stabil und heute die offizielle Empfehlung.
Vielleicht fragen Sie sich: „Muss ich den App Router überhaupt lernen? Ist das wieder so ein umständliches Neues?”
Genau diese Unsicherheit soll dieser Artikel auflösen. Ich erkläre die Kernkonzepte des App Router so einfach wie möglich – Server Components, Spezialdateien und der Unterschied zum Pages Router. Danach können Sie schnell einsteigen und typische Stolpersteine vermeiden.
Was ist der App Router – und warum ihn nutzen?
Kurz gesagt: Der App Router ist das neue Routing-System von Next.js ab v13. Er baut auf Reacts Server Components auf und ist moderner sowie flexibler als der Pages Router.
Im Vergleich zum alten Pages Router gibt es drei klare Vorteile:
1. Bessere Performance
Der App Router nutzt standardmäßig Server Components. Der Großteil des Codes läuft auf dem Server, der Browser lädt weniger JavaScript – Seiten werden schneller. Laut Vercel-Bericht 2024 sind über 60 % der Top-Next.js-Apps bereits auf den App Router umgestiegen.
"Über 60 % der Top-Next.js-Apps sind bereits auf den App Router umgestiegen"
2. Flexibleres Layout-System
Im Pages Router sind verschachtelte Layouts umständlich. Im App Router reicht layout.js – und beim Seitenwechsel wird das Layout nicht neu gerendert. Sehr flüssig.
3. Stärkeres Error-Handling und Loading
Mit loading.js definieren Sie Ladeanimationen, mit error.js fangen Sie Fehler ab und zeigen Fallback-UI. Im Pages Router bauen Sie das selbst; im App Router ist es konventionell vorgegeben.
Geht der Pages Router noch? Ja.
Beide Systeme können parallel existieren. Wer Next.js neu lernt, sollte aber direkt mit dem App Router starten – offizielle Empfehlung, und ab v14.1.4 ist er im Scaffold-Standard.
Dateisystem-Routing: vom Ordner zur Seite
Das zentrale Konzept: Ihre Ordnerstruktur ist Ihre Routenstruktur.
Ein Beispiel:
app/
├── page.js # Startseite → /
├── about/
│ └── page.js # Über-Seite → /about
└── blog/
├── page.js # Blog-Liste → /blog
└── [slug]/
└── page.js # Blog-Detail → /blog/:slug
Wichtige Punkte:
1. page.js ist der Routen-Einstieg
Nur Dateien namens page.js werden als Seiten ausgeliefert. Andere (layout.js, loading.js usw.) sind Hilfsdateien und nicht direkt erreichbar.
2. Dynamische Routen mit eckigen Klammern
Für /blog/hello-world legen Sie app/blog/[slug]/page.js an – slug kommt automatisch in der Komponente an:
// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
return <h1>Artikel: {params.slug}</h1>
}
3. Catch-all mit [...slug]
Für mehrstufige Pfade wie /docs/a/b/c: app/docs/[...slug]/page.js – params.slug ist dann ['a', 'b', 'c'].
Vergleich Pages Router:
Früher: pages/blog/[id].js. App Router: app/blog/[id]/page.js – ein Ordner mehr. Grund: Platz für layout.js, loading.js und andere Spezialdateien pro Route.
Am Anfang wirkt das umständlich; mit der Zeit wird die Projektstruktur deutlich klarer.
Server Components vs. Client Components: das Kernkonzept
Das verwirrt Anfänger am meisten – mir ging es genauso.
Kurz: Im App Router laufen Komponenten standardmäßig auf dem Server; nur bei Interaktion im Browser.
Standard: Server Component
Komponenten unter app/ sind standardmäßig Server Components. Sie werden serverseitig gerendert, der Browser bekommt HTML.
Vorteile:
- Weniger JavaScript: Code bleibt auf dem Server, kleinere JS-Bundles
- Direkter Backend-Zugriff: DB-Queries, API-Keys sicher auf dem Server
- Schneller First Paint: vorgefertigtes HTML, kurze FCP (First Contentful Paint)
Typisches Beispiel:
// app/products/page.js
// Server Component – läuft auf dem Server
async function getProducts() {
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
<h1>Produktliste</h1>
{products.map(p => (
<div key={p.id}>{p.name}</div>
))}
</div>
)
}
Direkt async/await für Daten – ohne useEffect oder getServerSideProps.
Wann Client Component?
Manchmal muss Code im Browser laufen:
- React Hooks (
useState,useEffect) - Benutzerinteraktion (
onClick,onChange) - Browser-APIs (
localStorage,window)
Dann Client Component – eine Zeile 'use client' oben in der Datei:
// components/AddToCartButton.js
'use client' // Als Client Component markieren
import { useState } from 'react'
export default function AddToCartButton({ productId }) {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
In den Warenkorb ({count})
</button>
)
}
Gemischt nutzen: Best Practice
Produktseite als Beispiel:
- Produktliste: Server Component (Daten serverseitig, weniger JS)
- Warenkorb-Button: Client Component (Klick-Interaktion)
// app/products/page.js (Server Component)
import AddToCartButton from '@/components/AddToCartButton' // Client Component
async function getProducts() {
// Daten serverseitig laden
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
<h1>Produktliste</h1>
{products.map(p => (
<div key={p.id}>
{p.name}
<AddToCartButton productId={p.id} />
</div>
))}
</div>
)
}
Merksatz: Standard Server Component. 'use client' nur, wenn Interaktion wirklich nötig ist.
Nicht gleich alles mit 'use client' markieren – dann verlieren Sie den Vorteil des App Router.
Spezialdateien: professionellere Projekte
Der App Router definiert Dateinamen wie layout.js, loading.js, error.js. Erst wirkt das viel – in der Praxis sehr angenehm.
layout.js: gemeinsames Layout
Definiert das Layout eines Route-Segments und umschließt gleich- und untergeordnete Seiten.
Navigation und Footer für die ganze App:
// app/layout.js (Root-Layout)
export default function RootLayout({ children }) {
return (
<html lang="de">
<body>
<nav>Navigation</nav>
<main>{children}</main>
<footer>Footer</footer>
</body>
</html>
)
}
Verschachtelte Layouts:
app/
├── layout.js # Global (Nav + Footer)
├── page.js # Startseite
└── dashboard/
├── layout.js # Dashboard (Sidebar)
├── page.js # /dashboard
└── settings/
└── page.js # /dashboard/settings
Wechsel von /dashboard zu /dashboard/settings: globales und Dashboard-Layout bleiben, nur page.js aktualisiert sich.
loading.js: Ladezustand
Kein manuelles Loading-State mehr nötig. loading.js anlegen – Next.js wrappt die Seite mit Suspense:
// app/dashboard/loading.js
export default function Loading() {
return <div>Laden…</div>
}
Während des Datenladens erscheint dieser Inhalt automatisch.
error.js: Error Boundary
Fängt Seitenfehler ab und zeigt Fallback-UI:
// app/dashboard/error.js
'use client' // Error Boundaries müssen Client Components sein
export default function Error({ error, reset }) {
return (
<div>
<h2>Fehler: {error.message}</h2>
<button onClick={reset}>Erneut versuchen</button>
</div>
)
}
Fallstrick: error.js fängt Fehler im gleichstufigen layout.js nicht ab. React Error Boundaries greifen nur bei Kind-Komponenten, nicht bei sich selbst oder Eltern.
Layout-Fehler: error.js im übergeordneten Ordner oder global-error.js im Root.
not-found.js: 404-Seite
Bei unbekannter Route:
// app/not-found.js
export default function NotFound() {
return <h1>Seite nicht gefunden</h1>
}
404 auch programmatisch auslösen:
import { notFound } from 'next/navigation'
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
if (!post) notFound() // not-found.js anzeigen
return <article>{post.title}</article>
}
Datei-Hierarchie
layout.js
├── loading.js (Suspense-Grenze)
│ └── page.js
└── error.js (Error-Grenze)
layout außen; error.js umschließt es nicht. loading.js für Ladezustand, error.js für Fehler.
Das zu verstehen spart viele Stolpersteine.
Datenzugriff: Schluss mit getServerSideProps
Im Pages Router: getServerSideProps oder getStaticProps – separate Export-Funktion, umständliche Datenweitergabe.
Der App Router vereinfacht das.
Direkt async/await
In Server Components Daten in der Komponente laden:
// app/posts/page.js
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Normales async/await – keine spezielle API.
Paralleles Laden
Mehrere Quellen parallel:
export default async function Dashboard() {
// Parallel – kein gegenseitiges Blockieren
const [user, posts, stats] = await Promise.all([
getUser(),
getPosts(),
getStats()
])
return (
<div>
<h1>{user.name}</h1>
<Posts data={posts} />
<Stats data={stats} />
</div>
)
}
Cache und Revalidierung
Next.js cached fetch standardmäßig. Steuerung:
// Nach 60 Sekunden revalidieren
fetch('https://api.example.com/data', {
next: { revalidate: 60 }
})
// Kein Cache – immer frische Daten
fetch('https://api.example.com/data', {
cache: 'no-store'
})
Vergleich Pages Router:
- Pages Router:
getServerSideProps+getStaticProps, separate Export-Funktion - App Router:
async/awaitin der Komponente
Deutlich einfacher.
Häufige Anfängerfragen
Beim Lernen des App Router bin ich in typische Fallen gelaufen. Hier die häufigsten Fragen.
Frage 1: Wann 'use client'?
Unsicherheit: In Tutorials überall 'use client' – wann wirklich nötig?
Antwort:
Standard: nicht setzen. Nur bei Bedarf.
'use client' nur wenn:
- React Hooks (
useState,useEffect,useContext) - Benutzerinteraktion (
onClick,onChange) - Browser-APIs (
window,localStorage)
Sonst weglassen. Server Components sind performanter und erlauben direkten Backend-Zugriff.
Frage 2: layout.js und page.js?
Unsicherheit: Beide im selben Ordner – wer umschließt wen?
Antwort:
layout.js umschließt page.js und Unterrouten.
app/
├── layout.js # Umschließt alle Seiten darunter
├── page.js # Startseite
└── about/
└── page.js # Über-Seite – ebenfalls im Layout
Beim Seitenwechsel bleibt layout.js – nur page.js wechselt. Deshalb flackert die Navigation nicht.
Frage 3: Dynamische Parameter?
Unsicherheit: [slug]/page.js angelegt – wie kommt slug in die Komponente?
Antwort:
Über params:
// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
console.log(params.slug) // Wert aus der URL
return <h1>Artikel: {params.slug}</h1>
}
Verschachtelt, z. B. app/blog/[category]/[slug]/page.js:
export default function Post({ params }) {
console.log(params.category, params.slug)
return <h1>{params.category} - {params.slug}</h1>
}
Frage 4: error.js greift nicht?
Unsicherheit: error.js existiert, Layout-Fehler werden nicht gefangen.
Antwort:
error.js fängt Fehler im gleichstufigen layout.js nicht ab – React Error Boundary.
Zwei Wege:
error.jsim übergeordneten Verzeichnisglobal-error.jsim Root (mit<html>und<body>)
// app/global-error.js
'use client'
export default function GlobalError({ error, reset }) {
return (
<html>
<body>
<h2>Globaler Fehler: {error.message}</h2>
<button onClick={reset}>Erneut versuchen</button>
</body>
</html>
)
}
Frage 5: Altes Projekt migrieren?
Unsicherheit: So viel Neues – muss alles umgebaut werden?
Antwort:
Keine Eile.
Pages Router und App Router parallel:
- Bestehendes in
pages/ - Neues in
app/
Vercel unterstützt den Pages Router langfristig.
Neue Projekte: direkt App Router – das ist die Richtung, das Ökosystem wächst.
Fazit
Die fünf Kernkonzepte des App Router:
- Dateisystem-Routing: Ordnerstruktur = Routen,
page.jsist Einstieg - Server Components: Standard serverseitig, bessere Performance
- Client Components:
'use client'bei Interaktion - Spezialdateien:
layout.js,loading.js,error.jsfür professionelle Struktur - Datenzugriff:
async/awaitstattgetServerSideProps
Der App Router ist die Zukunft von Next.js. Vercel investiert weiter, die Community folgt. Wer Next.js neu lernt, startet am besten hier.
Als Nächstes: ausprobieren. Kleines Projekt – Blog oder Todo-App mit App Router. Konzepte versteht man am besten durch eigenen Code.
Bei Problemen: Pages Router und App Router können parallel laufen; im Zweifel erst Pages Router, später schrittweise migrieren.
Die Next.js-Docs wirken manchmal unübersichtlich, der App-Router-Abschnitt ist aber solide. Bei konkreten Fragen: Docs oder GitHub Discussions.
Viel Erfolg beim Lernen!
FAQ
Wann sollte ich 'use client' verwenden?
Wie hängen layout.js und page.js zusammen?
Wie lese ich dynamische Routenparameter aus?
Warum fängt error.js Fehler in layout.js nicht ab?
Muss ich ein altes Projekt auf den App Router migrieren?
Was sind die Hauptunterschiede zwischen App Router und Pages Router?
9 Min. Lesezeit · Veröffentlicht am: 18. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Du liest den ersten Beitrag dieser Serie. Lies den nächsten Beitrag oder öffne die Serienübersicht, um den gesamten Pfad zu sehen.
Vorheriger
Du bist am Anfang dieser Serie.
Nächster
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe
Praxisbeispiel mit Next.js 15, Server Actions und Prisma: Schritt für Schritt ein produktionsreifes Full-Stack-Blog-System an einem Wochenende – mit vollständigem Code, Fallstricken und Performance-Optimierung.
Teil 2 von 51
Ähnliche Beiträge
Next.js Pages Router zu App Router: Praxisleitfaden mit Migrationsstrategie und Fallen-Checkliste

Next.js Pages Router zu App Router: Praxisleitfaden mit Migrationsstrategie und Fallen-Checkliste
Next.js Advanced Routing: Route Groups, Nested Layouts, Parallel Routes und Intercepting Routes


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen