Zum Inhalt springen

8.1 Next.js: Projekt, Routing und Layouts

Zu Zen-Modus wechseln

Nach dieser Einheit kannst du:

  • den Unterschied zwischen Bibliothek und Framework erklären und nennen, was Next.js gegenüber reinem React übernimmt.
  • ein Next.js-Projekt mit TypeScript und App Router anlegen, starten und seine Struktur erklären.
  • Seiten über die Ordnerstruktur anlegen und dynamische Segmente wie [id] auslesen.
  • gemeinsame Rahmen als verschachtelte Layouts umsetzen und mit Link navigieren.

Wer ein Regal braucht, kauft Bretter und Schrauben und baut, wie er will: Die Werkzeuge warten, bis man sie in die Hand nimmt. Wer ein Fertighaus bestellt, bekommt einen Plan mit Wänden, Leitungen und Stiegenhaus; man entscheidet über Fenster, Farben und Möbel, aber nicht mehr, wo das Stiegenhaus hinkommt. React aus Kapitel 7 ist das Werkzeug: Unser Code ruft es auf. Next.js ist das Fertighaus: Es gibt die Struktur vor und ruft unseren Code an den vorgesehenen Stellen auf.

flowchart LR
    accTitle: Bibliothek und Framework im Vergleich
    subgraph lib["Bibliothek: React"]
        direction TB
        own1["dein Code"] -- "ruft auf" --> react["React"]
    end
    subgraph fw["Framework: Next.js"]
        direction TB
        next["Next.js"] -- "ruft auf" --> own2["dein Code in<br/>app/.../page.tsx"]
    end

Abb. 8.1: Eine Bibliothek wird von deinem Code aufgerufen, ein Framework ruft deinen Code auf (eigene Darstellung).

Diese Umkehrung der Kontrolle ist das Wesen jedes Frameworks; sie erklärt, warum ein Framework Konventionen hat, die man lernt, statt sie zu diskutieren. Der Lehrplan verlangt die Entwicklung von Anwendungen “unter Verwendung von Frameworks und Middleware”, und unser Framework ist Next.js: der verbreitete Rahmen um React, der Client und Server in einem Projekt vereint. Er bringt mit, was React fehlt: mehrere Seiten mit URLs, einen Server, der sie ausliefert, einen fertig eingerichteten Build und später einen Ort für die eigene API.

Damit endet die Vite-Phase aus Kapitel 7. Deine Komponenten wandern unverändert mit: JSX, Props, State und Hooks bleiben dieselben. Neu sind die Ordnerstruktur, das Routing und die Frage, ob eine Komponente am Server oder im Browser läuft; die klärt Einheit 8.2.

  • Framework: ein Rahmen, der die Struktur einer Anwendung vorgibt und deinen Code an festgelegten Stellen aufruft.
  • Route: eine aufrufbare Adresse der Anwendung, etwa /jobs.
  • Segment: ein Teil des Pfads zwischen zwei Schrägstrichen; /jobs/42 hat die Segmente jobs und 42.
  • Dynamisches Segment: ein Platzhalter im Pfad, im Ordnernamen in eckigen Klammern, etwa [id].
  • Layout: ein Rahmen, der alle Seiten eines Ordners und seiner Unterordner umschließt.
  1. Projekt erzeugen. Der Assistent create-next-app fragt sonst eine Reihe von Einstellungen ab; im Kurs geben wir sie gleich als Optionen mit, damit alle dasselbe Projekt bekommen:

    Terminal-Fenster
    npx create-next-app@latest render-farm --ts --app --biome --no-tailwind --no-src-dir --no-react-compiler --cache-components --import-alias "@/*" --use-npm --yes
  2. Entwicklungsserver starten:

    Terminal-Fenster
    cd render-farm
    npm run dev

    Die Anwendung läuft auf http://localhost:3000 und aktualisiert sich bei jeder gespeicherten Änderung selbst.

  3. Struktur ansehen. Die wichtigsten Bestandteile:

    render-farm/
    ├── app/ # pages, layouts, later API routes
    │ ├── layout.tsx # root layout (html, body, navigation)
    │ └── page.tsx # the start page /
    ├── public/ # static files (images, favicon)
    ├── package.json # scripts and dependencies
    ├── next.config.ts # framework configuration, cacheComponents: true
    ├── biome.json # linter and formatter, as in chapter 1
    └── tsconfig.json # TypeScript, preconfigured strict

Beispielprojekt: 08_nextjs_struktur_routing_examples/ex01_app_router

Die Optionen im Überblick:

OptionBedeutung
--ts, --appTypeScript und der App Router (Ordner app/)
--biomeBiome als Linter und Formatierer, wie seit Kapitel 1
--no-tailwind, --no-src-dirkein Tailwind, der Ordner app/ liegt direkt im Projekt
--no-react-compilereine automatische Optimierung, die der Kurs nicht verwendet
--cache-componentsCache Components: Next.js rendert jede Seite beim Build vor und verlangt, dass du Daten ausdrücklich zwischenspeicherst oder pro Anfrage lädst (Einheit 8.2)
--import-alias "@/*", --use-npm, --yesImporte ab Projektwurzel mit @/, npm als Paketverwaltung, alle übrigen Fragen mit dem Standardwert

Bemerkenswert ist, was fehlt: kein Bundler-Setup, keine tsc-Aufrufe, kein Server-Skript. Das Framework übernimmt Build, TypeScript und Serverbetrieb; npm run dev für die Entwicklung, npm run build und npm start für den Produktionsbetrieb. Beim ersten npm run dev legt Next.js zusätzlich eine Datei AGENTS.md mit Hinweisen für KI-Assistenten an; sie darf bleiben.

Next.js leitet die URLs der Anwendung aus der Ordnerstruktur unter app/ ab. Ein Ordner ist ein URL-Segment, die Datei page.tsx darin macht das Segment zur aufrufbaren Seite:

app/
├── page.tsx -> /
├── jobs/
│ ├── page.tsx -> /jobs
│ └── [id]/
│ └── page.tsx -> /jobs/42, /jobs/7, ...
└── presets/
└── page.tsx -> /presets
flowchart LR
    accTitle: Ordner unter app werden zu Routen
    app["app/"] --> p0["page.tsx<br/>/"]
    app --> jobs["jobs/"]
    jobs --> p1["page.tsx<br/>/jobs"]
    jobs --> id["[id]/"]
    id --> p2["page.tsx<br/>/jobs/42"]
    app --> presets["presets/"]
    presets --> p3["page.tsx<br/>/presets"]

Abb. 8.2: Ein Ordner ist ein Segment, erst page.tsx macht daraus eine Seite (eigene Darstellung).

Eine Seite ist eine gewöhnliche React-Komponente als Standardexport:

app/jobs/page.tsx
export default function JobsPage() {
return <h1>Render jobs</h1>;
}

Dynamische Segmente stehen in eckigen Klammern: Der Ordner [id] fängt jeden Wert an dieser Stelle, und die Seite erhält ihn über die Prop params. In aktuellen Next.js-Versionen ist params ein Promise, daher async und await:

app/jobs/[id]/page.tsx
export default async function JobDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <h1>Job {id}</h1>;
}

Die ID steht erst fest, wenn jemand die Seite aufruft. Next.js rendert mit Cache Components aber jede Seite schon beim Build vor (Einheit 8.2) und braucht für den Teil, der noch fehlt, einen Platzhalter. Den liefert eine Datei loading.tsx im selben Ordner; ohne sie bricht npm run build mit der Meldung “Next.js encountered uncached or runtime data during prerendering” ab.

// app/jobs/[id]/loading.tsx: shown until the id is known
export default function Loading() {
return <p>Loading job ...</p>;
}

Beispielprojekt: 08_nextjs_struktur_routing_examples/ex01_app_router

Damit lässt sich das URL-Schema aus Kapitel 6 direkt abbilden: Sammlung unter /jobs, Element unter /jobs/42. Der Wert in id ist immer ein String, auch wenn er wie eine Zahl aussieht. Für unbekannte Pfade liefert Next.js automatisch eine 404-Seite; eine eigene Datei app/not-found.tsx gestaltet sie, und die Funktion notFound() aus next/navigation löst sie aus, etwa wenn es den Job mit dieser ID nicht gibt. Genauer erklärt: Weil der Platzhalter aus loading.tsx dann schon unterwegs ist, antwortet der Server mit Status 200 statt 404; Next.js markiert die Seite dafür mit noindex, damit Suchmaschinen sie nicht aufnehmen.

Wo begegnet dir das? In fast jeder Adresszeile: Ein Online-Shop zeigt Produkte unter Adressen wie /produkte/12345. Es gibt nicht hunderttausend Seiten-Dateien, sondern eine einzige für /produkte/[id], die jedes Produkt anzeigt.

Auflösung

(a) app/about/page.tsx. (b) app/presets/[name]/page.tsx, der Name kommt in params. (c) app/jobs/[id]/log/page.tsx: ein normales Segment unter dem dynamischen. (d) app/not-found.tsx.

Eine Datei layout.tsx umschließt alle Seiten ihres Ordners und aller Unterordner. Das Wurzel-Layout trägt das HTML-Gerüst und die Navigation, die auf jeder Seite erscheint:

app/layout.tsx
import Link from "next/link";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="de">
<body>
<nav>
<Link href="/">Start</Link>
<Link href="/jobs">Jobs</Link>
<Link href="/presets">Presets</Link>
</nav>
<main>{children}</main>
</body>
</html>
);
}

children ist die jeweils aktive Seite; das Layout selbst bleibt beim Seitenwechsel erhalten, samt seinem Zustand, etwa einem geöffneten Menü. Layouts verschachteln sich: Ein zusätzliches app/jobs/layout.tsx umschließt nur die Jobs-Seiten, innerhalb des Wurzel-Layouts.

// app/jobs/layout.tsx: wraps only the job pages, inside the root layout
export default function JobsLayout({ children }: { children: React.ReactNode }) {
return (
<section>
<p>Job area</p>
{children}
</section>
);
}
flowchart TD
    accTitle: Verschachtelte Layouts um eine Seite
    root["app/layout.tsx<br/>html, body, Navigation"] --> jl["app/jobs/layout.tsx<br/>Job area"]
    jl --> page["app/jobs/[id]/page.tsx<br/>Job 42"]
    root --> pp["app/presets/page.tsx<br/>ohne Job-Rahmen"]

Abb. 8.3: Das Wurzel-Layout umschließt alles, das Bereichs-Layout nur die Seiten unter jobs/ (eigene Darstellung).

Navigiert wird mit der Komponente Link statt <a>:

sequenceDiagram
    accTitle: Seitenwechsel mit a und mit Link
    participant b as Browser
    participant s as Server
    Note over b: a href=/jobs
    b->>s: ganze Seite /jobs anfordern
    s-->>b: komplettes HTML, alles neu, Zustand weg
    Note over b: Link href=/jobs
    b->>s: nur Inhalt für /jobs
    s-->>b: neue Seite für children, Layout bleibt

Abb. 8.4: Mit <a> lädt der Browser alles neu, mit Link tauscht Next.js nur den Inhalt (eigene Darstellung).

Dieses Wurzel-Layout mit Navigation ist ab jetzt das gemeinsame Gerüst, in dem alle weiteren Übungen dieses Jahres wohnen.

Auflösung

Mit <a> ist der Text weg: Der Browser lädt die ganze Seite neu, auch das Layout, und jeder Zustand im Speicher geht verloren. Mit Link bleibt “intro” stehen: Nur der Inhaltsbereich (children) wird ausgetauscht, das Layout mit dem Suchfeld bleibt, wie es ist.

  1. Datei heißt nicht page.tsx: app/jobs/jobs.tsx erzeugt keine Route; nur page.tsx macht einen Ordner aufrufbar.
  2. params ohne await: In aktuellen Versionen ist params ein Promise; bei params.id meldet TypeScript TS2339: Property 'id' does not exist on type 'Promise<{ id: string; }>'.
  3. loading.tsx fehlt bei [id]: npm run build bricht ab, weil die Seite ohne Platzhalter nicht vorgerendert werden kann.
  4. id als Zahl behandelt: Werte aus dem Pfad sind Strings; id === 42 ist immer falsch.
  5. <a> für interne Ziele: Jeder Klick lädt die ganze Seite neu, Layout-Zustand geht verloren.
  6. Assistent ohne Optionen: Mit den Standardwerten entsteht ein Projekt mit Tailwind und anderen Einstellungen als in den Beispielen des Kurses.
  • Next.js: Installation - Projekt anlegen, Systemvoraussetzungen, Projektstruktur.
  • Next.js: Layouts and Pages - Seiten, Layouts, dynamische Segmente und Links im Überblick.
  • Next.js: create-next-app - alle Optionen des Assistenten.
  • Next.js: Link-Komponente - Vorladen, aktiver Link und weitere Props.

Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Codebeispiele und Ordnerbäume liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.

Du kannst jetzt:

  • den Unterschied zwischen Bibliothek und Framework erklären und nennen, was Next.js gegenüber reinem React übernimmt.
  • ein Next.js-Projekt mit TypeScript und App Router anlegen, starten und seine Struktur erklären.
  • Seiten über die Ordnerstruktur anlegen und dynamische Segmente wie [id] auslesen.
  • gemeinsame Rahmen als verschachtelte Layouts umsetzen und mit Link navigieren.
  • Aufgabe 08-1a - Mehrseiter mit Next.js, Stufe Wesentlich_überwiegend
  • Projektanlage, Optionen des Assistenten → Next.js: create-next-app; Projektinhalt, Turbopack im Build, AGENTS.md beim ersten npm run dev → eigener Test mit create-next-app und Next.js 16.4.0 (Oktober 2026)
  • Seiten, Layouts, dynamische Segmente, params als Promise → Next.js: Layouts and Pages, Dynamic Route Segments; notFound() → Next.js: notFound
  • Meldung TS2339 bei params.id ohne await, Build-Abbruch ohne loading.tsx → eigener Test mit Next.js 16.4.0 und Cache Components (Oktober 2026)
  • loading.tsx als Platzhalter → Next.js: loading.js; Status 200 und noindex bei notFound() nach Beginn des Streamings → Next.js: notFound und eigener Test
  • Link, Vorladen, usePathname → Next.js: Link-Komponente
  • Turbopack → Next.js: Turbopack
  • Lehrplanbezug (Frameworks und Middleware) → Lehrplan der Höheren Lehranstalt für Informationstechnologie, B.2 Medientechnik, 2.1 Softwareentwicklung, IV. Jahrgang (zitiert auf der Kursseite Lehrplan und Ablauf)
  • Abb. 8.1 (Bibliothek und Framework), Abb. 8.2 (Ordner und Routen), Abb. 8.3 (verschachtelte Layouts), Abb. 8.4 (a und Link) → eigene Darstellung, alle als Mermaid-Diagramm
  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules