
Next.js App Router Architektur für Admin & Public

Wenn du ein professionelles Headless CMS baust, ist eine saubere Next.js App Router Architektur der absolute Schlüssel zum Erfolg. In den vorherigen Teilen unserer Masterclass haben wir ein hochsicheres Laravel-Backend mit Spatie-Rollen gebaut und ein wunderschönes Redaktions-Dashboard mit shadcn/ui aufgesetzt. Doch bisher haben wir einen entscheidenden architektonischen Konflikt in unserem Frontend ignoriert: Wir versuchen, zwei völlig unterschiedliche Applikationen in denselben Topf zu werfen.
Denk einmal genau darüber nach, was unser System leisten muss. Auf der einen Seite haben wir das Admin-Dashboard. Hier arbeiten Redakteure. Die Daten müssen zwingend in Echtzeit synchronisiert sein, jeder einzelne Request muss durch unseren strengen Laravel Sanctum-Cookie-Tresor authentifiziert werden, und SEO (Suchmaschinenoptimierung) spielt absolut keine Rolle. Hier dominiert Dynamic Rendering (Serverseitiges Rendering bei jedem einzelnen Request).
Auf der anderen Seite haben wir das öffentliche Blog-Frontend (Public). Hier lesen deine Endnutzer die fertigen Artikel. Diese Seite muss für Google perfekt lesbar sein, sie muss in Millisekunden laden und sie ist völlig öffentlich zugänglich. Wenn 10.000 Nutzer gleichzeitig deinen neuesten Artikel anklicken, darf dein Laravel-Backend das nicht einmal spüren. Hier dominiert Static Site Generation (SSG) und extrem aggressives Caching.
Wie vereinen wir diese beiden extremen Gegensätze in einem einzigen Next.js-Projekt, ohne dass sich die Layouts, Authentifizierungs-Checks und CSS-Styles gegenseitig blockieren oder überschreiben?
Die Antwort ist eines der mächtigsten Features der modernen Frontend-Entwicklung: Next.js Route Groups.
1. Das Konzept: Unsichtbare Ordner (Route Groups)
Bevor der moderne App Router (ab Next.js 13) eingeführt wurde, war das Routing in React-Frameworks strikt an die physische Ordnerstruktur gebunden. Wenn du einen Ordner admin und einen Ordner blog hattest, lauteten die URLs zwingend /admin und /blog. Das führte oft dazu, dass man globale Layout-Dateien (_app.js) mit extrem komplexen und unleserlichen if/else-Abfragen vollmüllen musste, um je nach URL ein komplett anderes Design zu laden.
Die moderne Next.js App Router Architektur löst dieses Problem durch sogenannte Route Groups. Wenn du einen Ordnernamen in runde Klammern setzt – zum Beispiel (admin) oder (public) – wird dieser Ordnername von Next.js bei der Generierung der finalen URL im Browser komplett ignoriert. Er ist unsichtbar. Er dient ausschließlich dazu, Dateien für uns Entwickler logisch zu gruppieren und ihnen jeweils eigene, völlig unabhängige layout.tsx-Dateien zuzuweisen.
Das bedeutet: Wir können ein Layout für den Admin-Bereich bauen, das die Navigation, die Sidebar und die strengen Session-Checks lädt. Und wir können ein völlig anderes Layout für den Public-Bereich bauen, das einen schönen Footer, SEO-Metadaten und Google Analytics lädt. Beide Welten existieren friedlich nebeneinander in derselben Codebase.

2. Die Festung umbauen: Die neue Ordnerstruktur (Fortsetzung)
Machen wir genau da weiter, wo wir aufgehört haben. Du hast deinen bestehenden admin-Ordner nun erfolgreich in die neue Route Group (admin) verschoben. Die Dateistruktur deines Dashboards sieht jetzt so aus:
1src/
2└── app/
3 ├── (admin)/
4 │ └── admin/
5 │ ├── dashboard/page.tsx
6 │ ├── posts/page.tsx
7 │ └── layout.tsx <-- Unser Admin-Dashboard Layout!Diese simple Klammer-Notation ist das Herzstück der Next.js App Router Architektur. Sie sagt dem Server: "Wende das Admin-Layout (mit der Sidebar, dem Logout-Button und den strengen Sanctum-Checks) NUR auf Routen an, die physisch innerhalb des (admin) Ordners liegen."
3. Die öffentliche Bühne: Das Blog-Layout erstellen
Jetzt bauen wir die völlig isolierte Welt für deine Leser auf. Navigiere in deinen neuen (public) Ordner. Hier erstellen wir nun ein komplett eigenes, SEO-freundliches Layout, das absolut nichts mit dem Admin-Bereich zu tun hat. Es lädt keine Sidebar und führt keine komplizierten Rechte-Prüfungen durch. Es ist auf reine Geschwindigkeit getrimmt.
Erstelle die Datei src/app/(public)/layout.tsx und füge diesen Code ein:
1import Link from 'next/link';
2
3export default function PublicLayout({
4 children,
5}: {
6 children: React.ReactNode;
7}) {
8 return (
9 <div className="min-h-screen bg-white text-zinc-900 flex flex-col font-sans">
10
11 {/* Der öffentliche Header für Leser */}
12 <header className="border-b border-zinc-200 bg-white sticky top-0 z-50">
13 <div className="max-w-5xl mx-auto px-6 h-16 flex items-center justify-between">
14 <Link href="/" className="text-2xl font-black tracking-tighter text-zinc-900">
15 Dev<span className="text-blue-600">Blog.</span>
16 </Link>
17 <nav className="hidden md:flex gap-6">
18 <Link href="/" className="text-sm font-medium hover:text-blue-600 transition-colors">Startseite</Link>
19 <Link href="/about" className="text-sm font-medium hover:text-blue-600 transition-colors">Über mich</Link>
20 </nav>
21 </div>
22 </header>
23
24 {/* Der Inhaltsbereich (Hier landen später die Blog-Artikel) */}
25 <main className="flex-1 max-w-5xl mx-auto w-full px-6 py-12">
26 {children}
27 </main>
28
29 {/* Der öffentliche Footer */}
30 <footer className="border-t border-zinc-200 bg-zinc-50 py-8">
31 <div className="max-w-5xl mx-auto px-6 text-center text-sm text-zinc-500">
32 <p>© {new Date().getFullYear()} DevBlog. Alle Rechte vorbehalten.</p>
33 <p className="mt-2">
34 Powered by Laravel 12 & Next.js App Router
35 </p>
36 </div>
37 </footer>
38 </div>
39 );
40}Schau dir die Struktur an! Während unser Admin-Bereich komplett im Dark-Mode (bg-zinc-950) gehalten ist, nutzen wir für den öffentlichen Blog ein helles, lesefreundliches Design (bg-white).
Die Startseite (Index) anlegen
Damit wir dieses neue Layout auch testen können, brauchen wir eine Startseite. Da der Ordner (public) in der URL ignoriert wird, entspricht eine Datei unter src/app/(public)/page.tsx exakt der Root-URL deiner Website (http://localhost:3000/).
Erstelle genau diese Datei src/app/(public)/page.tsx:
1export default function HomePage() {
2 return (
3 <div>
4 <h1 className="text-5xl font-black mb-6">
5 Willkommen auf dem <span className="text-blue-600">Blog</span>.
6 </h1>
7 <p className="text-lg text-zinc-600 max-w-2xl">
8 Dies ist das blitzschnelle, öffentliche Frontend unseres Headless CMS.
9 Dank der mächtigen Next.js Route Groups beeinflusst dieser Bereich das
10 Admin-Dashboard in keinster Weise.
11 </p>
12 </div>
13 );
14}Wenn du jetzt im Browser http://localhost:3000/ aufrufst, siehst du das strahlend helle Blog-Design. Rufst du hingegen http://localhost:3000/admin/dashboard auf, tauchst du sofort wieder in den gesicherten, dunklen Admin-Tresor ab.
Das ist das Meisterstück der Next.js App Router Architektur. Keine chaotischen If-Abfragen mehr. Zwei völlig isolierte Welten in einer einzigen Codebase.

4. Die Blog-Startseite: Blazing Fast mit ISR
Unsere öffentliche Bühne steht. Doch aktuell ist die Startseite noch statisch und langweilig. Wir wollen dort natürlich unsere neuesten Artikel anzeigen, die wir im Admin-Dashboard geschrieben haben. Hier zeigt die Next.js App Router Architektur ihre absolut größte Stärke: Das native Daten-Fetching und Caching.
Erinnerst du dich an unseren serverFetch-Helfer aus dem (admin) Bereich? Dort haben wir mühsam die Sanctum-Cookies aus dem Request extrahiert und an Laravel weitergeleitet, um uns zu authentifizieren. Für die öffentliche Blog-Seite machen wir das nicht.
Der Blog ist öffentlich. Die Anfragen an die API sind anonym. Wir wollen hier keine Cookies senden, denn Cookies verhindern aggressives Caching. Wenn wir Millionen von Lesern haben, wollen wir, dass Next.js die API-Antwort einmal abruft, sie zwischenspeichert und an alle weiteren Leser aus dem ultraschnellen Edge-Cache ausliefert.
Dieses Konzept nennt sich ISR (Incremental Static Regeneration).
Öffne deine src/app/(public)/page.tsx und baue sie wie folgt um:
1import Link from 'next/link';
2
3// Wir nutzen die native, von Next.js erweiterte fetch-API
4async function getPublicPosts() {
5 const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/posts`, {
6 // DAS ist die Magie! Next.js cacht die Antwort für exakt 60 Sekunden.
7 next: { revalidate: 60 },
8 headers: {
9 'Accept': 'application/json',
10 }
11 });
12
13 if (!res.ok) {
14 throw new Error('Fehler beim Laden der Artikel');
15 }
16
17 return res.json();
18}
19
20export default async function HomePage() {
21 const { data: posts } = await getPublicPosts();
22
23 return (
24 <div>
25 <h1 className="text-5xl font-black mb-12">
26 Die neuesten <span className="text-blue-600">Artikel</span>.
27 </h1>
28
29 <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
30 {posts.map((post: any) => (
31 <article key={post.id} className="border border-zinc-200 rounded-2xl p-6 hover:shadow-lg transition-shadow bg-white">
32 <h2 className="text-2xl font-bold mb-3 text-zinc-900">
33 <Link href={`/p/${post.slug}`} className="hover:text-blue-600 transition-colors">
34 {post.title}
35 </Link>
36 </h2>
37 <p className="text-zinc-600 mb-4 line-clamp-3">
38 {/* Hier würden wir normalerweise einen bereinigten Excerpt anzeigen */}
39 {post.content}
40 </p>
41 <div className="flex items-center text-sm text-zinc-500">
42 <span>{post.author?.name || 'Redaktion'}</span>
43 <span className="mx-2">•</span>
44 <time>{new Date(post.created_at).toLocaleDateString('de-DE')}</time>
45 </div>
46 </article>
47 ))}
48 </div>
49 </div>
50 );
51}Warum diese Architektur das Spiel verändert
Lass uns kurz innehalten und verstehen, was das Objekt { next: { revalidate: 60 } } bewirkt.
Wenn der erste Besucher am Montagmorgen deinen Blog aufruft, sendet der Next.js Node-Server einen HTTP-Request an dein Laravel-Backend. Laravel fragt die MySQL-Datenbank ab, generiert das JSON und sendet es zurück. Next.js nimmt dieses JSON, rendert die HTML-Seite und speichert das fertige Ergebnis in seinem Hochgeschwindigkeits-Cache.
Wenn in den nächsten 60 Sekunden 50.000 weitere Nutzer auf deine Startseite gehen, bekommt dein Laravel-Backend davon absolut nichts mit. Next.js liefert einfach blitzschnell das fertig gerenderte HTML aus dem Cache aus. Deine Datenbank bleibt völlig entspannt.
Sobald die 60 Sekunden abgelaufen sind, löst der nächste Besucher im Hintergrund (!) einen neuen API-Request an Laravel aus, um zu prüfen, ob es neue Artikel gibt. Der Cache wird unbemerkt aktualisiert (revalidiert), ohne dass ein Nutzer jemals einen Lade-Spinner sehen muss. Das ist die Perfektion der entkoppelten Next.js App Router Architektur.

5. Dynamisches Routing: Die Einzelansicht und perfektes SEO
Unsere Leser können nun die Startseite bewundern, aber was passiert, wenn sie auf einen bestimmten Artikel klicken? Wir brauchen eine dynamische Unterseite, die anhand der URL (dem Slug) den passenden Artikel aus Laravel lädt. In der Next.js App Router Architektur passiert das über Ordnernamen in eckigen Klammern.
Erstelle im Frontend-Projekt folgende Ordnerstruktur: src/app/(public)/p/[slug]. Das [slug] signalisiert Next.js, dass dieser Teil der URL dynamisch ist (z. B. /p/mein-erster-artikel).
Erstelle darin die Datei page.tsx. Wir implementieren nun nicht nur die visuelle Darstellung, sondern auch die absolute Königsdisziplin für ein CMS: Dynamisches SEO. Wir wollen, dass Google und Social-Media-Plattformen (wie X oder LinkedIn) beim Teilen des Links sofort den richtigen Titel und das passende Bild anzeigen.
Füge diesen Code in deine src/app/(public)/p/[slug]/page.tsx ein:
1import { notFound } from 'next/navigation';
2import type { Metadata } from 'next';
3
4// 1. Unsere zentrale Daten-Fetch Funktion
5async function getPost(slug: string) {
6 // Wir feuern den Request an unseren Laravel Endpunkt für einen einzelnen Artikel
7 const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/posts/${slug}`, {
8 next: { revalidate: 60 }, // Auch hier nutzen wir das 60-Sekunden ISR-Caching!
9 headers: {
10 'Accept': 'application/json',
11 }
12 });
13
14 if (!res.ok) return null;
15 return res.json();
16}
17
18// 2. Die magische Next.js Metadata API für perfektes SEO
19export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
20 // Wir holen uns die Daten (Next.js dedupliziert diesen Aufruf automatisch!)
21 const post = await getPost(params.slug);
22
23 if (!post) {
24 return { title: 'Artikel nicht gefunden | DevBlog' };
25 }
26
27 // Hier bauen wir die perfekten Meta- und OpenGraph-Tags für Google und Co.
28 return {
29 title: `${post.data.title} | DevBlog`,
30 description: post.data.excerpt || 'Ein spannender Artikel auf unserem DevBlog.',
31 openGraph: {
32 title: post.data.title,
33 description: post.data.excerpt,
34 type: 'article',
35 publishedTime: post.data.published_at,
36 authors: [post.data.author?.name || 'Redaktion'],
37 }
38 };
39}
40
41// 3. Die eigentliche React Server Component für die Seite
42export default async function PostPage({ params }: { params: { slug: string } }) {
43 const post = await getPost(params.slug);
44
45 // Wenn der Artikel nicht existiert, triggern wir die native 404-Seite
46 if (!post) {
47 notFound();
48 }
49
50 return (
51 <article className="max-w-3xl mx-auto py-10">
52 <header className="mb-10 text-center">
53 <h1 className="text-4xl md:text-6xl font-black text-zinc-900 mb-6 tracking-tight leading-tight">
54 {post.data.title}
55 </h1>
56 <div className="flex items-center justify-center gap-4 text-zinc-500 font-medium">
57 <span>Geschrieben von {post.data.author?.name || 'Redaktion'}</span>
58 <span>•</span>
59 <time>{post.data.published_at}</time>
60 </div>
61 </header>
62
63 {/* Später nutzen wir hier ein Paket wie react-markdown, um den Inhalt perfekt zu stylen */}
64 <div className="prose prose-lg prose-blue mx-auto text-zinc-700 leading-relaxed">
65 <p>{post.data.content}</p>
66 </div>
67 </article>
68 );
69}Das Geheimnis der Fetch-Deduplizierung
Lass uns einen genauen Blick auf den obigen Code werfen, denn hier stolpern viele Anfänger. Dir ist sicher aufgefallen, dass wir die Funktion getPost(params.slug) zweimal aufrufen: Einmal in der Funktion generateMetadata (für SEO) und ein zweites Mal in unserer PostPage (für das Rendern der UI).
In einer klassischen React- oder Vue-App würde das bedeuten, dass wir bei jedem Seitenaufruf zwei identische HTTP-Requests an unser Laravel-Backend abfeuern. Eine massive Verschwendung von Ressourcen!
Doch die Next.js App Router Architektur ist extrem intelligent. Sie nutzt ein Konzept namens Fetch Deduplication (Deduplizierung). Während eines einzelnen Server-Render-Durchgangs merkt sich Next.js: "Hey, ich habe exakt diesen Fetch-Aufruf an Laravel gerade schon für die Metadaten gemacht! Ich frage Laravel nicht noch einmal, sondern gebe der PostPage einfach sofort das Ergebnis aus dem Zwischenspeicher."
Dadurch können wir unseren Code absolut sauber, modular und lesbar halten, generieren perfektes HTML für den Google Bot und belasten unsere Laravel REST API trotzdem nur mit einem einzigen, ressourcenschonenden Request.

6. Meisterhafte UX: Skeletons und Data Streaming
Wir haben unsere Daten-Fetches optimiert und das dynamische SEO eingerichtet. Doch lass uns kurz über User Experience (UX) sprechen. Wenn ein Besucher auf der Startseite auf einen Artikel klickt, muss der Next.js Server den Artikel (falls er nicht gecacht ist) vom Laravel-Backend abrufen, bevor er die neue Seite anzeigen kann. Das kann manchmal ein paar hundert Millisekunden dauern.
In traditionellen Single Page Applications (SPAs) hat man für diese Zeit mühsam State-Variablen (isLoading) angelegt und einen drehenden Ladekreis (Spinner) eingeblendet. In der modernen Next.js App Router Architektur lösen wir das völlig ohne JavaScript-State, und zwar durch ein Konzept namens Server-Side Streaming in Kombination mit einer magischen Datei namens loading.tsx.
Wenn du eine loading.tsx Datei in einem Ordner anlegst, umschließt Next.js die dazugehörige page.tsx automatisch mit einer React <Suspense> Boundary. Das bedeutet: Der Server sendet sofort – ohne jegliche Verzögerung – das Layout und den Lade-Zustand (das Skeleton) als HTML an den Browser. Während der Nutzer das Skeleton sieht, lädt der Server im Hintergrund die Daten aus dem Laravel-Backend fertig und "streamt" sie nahtlos in die bestehende Seite.
Den Skeleton Loader bauen
Lass uns das für unsere Artikel-Einzelansicht implementieren. Erstelle direkt neben deiner page.tsx im dynamischen Slug-Ordner (src/app/(public)/p/[slug]/) eine neue Datei namens loading.tsx:
1export default function Loading() {
2 return (
3 <article className="max-w-3xl mx-auto py-10 animate-pulse">
4 {/* Skeleton für den Header */}
5 <header className="mb-10 flex flex-col items-center">
6 {/* Titel-Platzhalter */}
7 <div className="h-12 bg-zinc-200 rounded-md w-3/4 mb-6"></div>
8 <div className="h-12 bg-zinc-200 rounded-md w-1/2 mb-6"></div>
9
10 {/* Meta-Daten Platzhalter */}
11 <div className="flex items-center gap-4">
12 <div className="h-4 bg-zinc-200 rounded-md w-32"></div>
13 <div className="h-4 w-4 bg-zinc-200 rounded-full"></div>
14 <div className="h-4 bg-zinc-200 rounded-md w-24"></div>
15 </div>
16 </header>
17
18 {/* Skeleton für den Textinhalt */}
19 <div className="space-y-4">
20 <div className="h-4 bg-zinc-200 rounded-md w-full"></div>
21 <div className="h-4 bg-zinc-200 rounded-md w-full"></div>
22 <div className="h-4 bg-zinc-200 rounded-md w-11/12"></div>
23 <div className="h-4 bg-zinc-200 rounded-md w-full"></div>
24 <div className="h-4 bg-zinc-200 rounded-md w-4/5"></div>
25 </div>
26
27 <div className="space-y-4 mt-8">
28 <div className="h-4 bg-zinc-200 rounded-md w-full"></div>
29 <div className="h-4 bg-zinc-200 rounded-md w-9/12"></div>
30 <div className="h-4 bg-zinc-200 rounded-md w-full"></div>
31 </div>
32 </article>
33 );
34}Dank der Tailwind-Klasse animate-pulse pulsieren diese grauen Boxen (bg-zinc-200) sanft.
Wenn du nun auf der Startseite deines Blogs auf einen Artikel klickst (besonders bei einer künstlich verlangsamten Netzwerkverbindung), passiert die Magie: Der Browser wartet nicht mehr. Die URL ändert sich sofort. Das Layout mit dem Header und Footer bleibt eisern stehen, der Inhaltsbereich zeigt für einen kurzen Moment das pulsierende Skeleton, und sobald Laravel die Daten liefert, rastet der echte Text nahtlos ein.
Das ist das absolute Nonplusultra der Frontend-Performance. Deine Leser haben das Gefühl, dass die Seite sofort reagiert, selbst wenn im Hintergrund gigantische Datenmengen verarbeitet werden. Durch die klare Trennung unserer Route Groups profitieren von diesem Lade-Design nur die Leser des Blogs – der Admin-Bereich bleibt völlig unberührt.

7. Das Sicherheitsnetz: Fehler und 404-Seiten elegant abfangen
Egal wie gut dein Laravel-Backend programmiert ist, im echten Leben passieren Fehler. Ein Nutzer vertippt sich in der URL, die Datenbank ist für einen Moment überlastet oder ein API-Endpunkt liefert unerwartete Daten. Wenn eine traditionelle React-App crasht, sieht der Nutzer oft nur einen furchteinflößenden "White Screen of Death" (eine komplett weiße, leere Seite).
Die Next.js App Router Architektur bietet uns hier ein geniales, dateibasiertes Rettungssystem. Erinnerst du dich an unseren Code aus der Artikel-Einzelansicht (Schritt 5)? Dort haben wir geschrieben: if (!post) { notFound(); }.
Doch wo führt dieser Aufruf hin? Standardmäßig liefert Next.js eine sehr simple, langweilige 404-Seite aus. Da wir uns aber in unserer abgetrennten (public) Route Group befinden, können wir eine maßgeschneiderte "Nicht gefunden"-Seite bauen, die das globale Blog-Layout (mit Header und Footer) beibehält!
Die not-found.tsx erstellen
Erstelle direkt im Ordner src/app/(public)/ eine neue Datei namens not-found.tsx:
1import Link from 'next/link';
2
3export default function PublicNotFound() {
4 return (
5 <div className="flex flex-col items-center justify-center py-20 text-center">
6 <h2 className="text-6xl font-black text-zinc-900 mb-4">404</h2>
7 <h3 className="text-2xl font-bold text-zinc-700 mb-6">
8 Huch! Dieser Artikel ist wohl verschwunden.
9 </h3>
10 <p className="text-zinc-500 mb-8 max-w-md">
11 Der gesuchte Link ist entweder veraltet, oder der Artikel wurde
12 von der Redaktion offline genommen.
13 </p>
14 <Link
15 href="/"
16 className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-colors"
17 >
18 Zurück zur Startseite
19 </Link>
20 </div>
21 );
22}Wenn ein Nutzer nun /p/gibt-es-nicht aufruft, fängt Next.js den Fehler ab und rendert diese wunderschöne Seite – exakt eingebettet in deinen Blog-Header und -Footer. Das Admin-Dashboard bleibt davon unberührt.
Die error.tsx für harte Server-Crashes
Was aber, wenn das Problem tiefgreifender ist? Stell dir vor, dein Laravel-Server ist offline (Status 500) und der fetch-Aufruf schlägt komplett fehl. Dafür gibt es die error.tsx.
Wichtig: Im Gegensatz zur page.tsx oder layout.tsx MUSS eine error.tsx immer eine React Client Component sein. Warum? Weil sie Fehler abfangen muss, die erst im Browser des Nutzers während der Interaktion entstehen können.
Erstelle die Datei src/app/(public)/error.tsx:
1'use client'; // Fehler-Grenzen müssen zwingend Client Components sein!
2
3import { useEffect } from 'react';
4
5export default function PublicError({
6 error,
7 reset,
8}: {
9 error: Error & { digest?: string };
10 reset: () => void;
11}) {
12 useEffect(() => {
13 // Hier könnten wir den Fehler an einen Dienst wie Sentry senden
14 console.error("Ein kritischer Fehler ist aufgetreten:", error);
15 }, [error]);
16
17 return (
18 <div className="flex flex-col items-center justify-center py-20 text-center bg-red-50/50 rounded-2xl border border-red-100">
19 <h2 className="text-3xl font-bold text-red-600 mb-4">
20 Da ist etwas schiefgelaufen!
21 </h2>
22 <p className="text-zinc-600 mb-8 max-w-md">
23 Wir haben aktuell Probleme, die Daten vom Server abzurufen.
24 Bitte versuche es in ein paar Minuten noch einmal.
25 </p>
26 <button
27 onClick={() => reset()} // Versucht, die aktuelle Route neu zu rendern
28 className="bg-red-600 hover:bg-red-700 text-white font-medium py-3 px-6 rounded-lg transition-colors"
29 >
30 Erneut versuchen
31 </button>
32 </div>
33 );
34}Die Magie hier liegt im reset()-Button. Wenn der Nutzer darauf klickt, versucht Next.js die fehlerhafte Server Component noch einmal neu auszuführen. Wenn Laravel in der Zwischenzeit wieder online ist, verschwindet die Fehlermeldung und die Seite lädt nahtlos nach. Und das Beste: Da wir Route Groups nutzen, kannst du für deinen (admin)-Bereich eine völlig andere, technisch viel detailliertere error.tsx anlegen, die den Redakteuren vielleicht sogar genaue API-Logs anzeigt, während deine Blog-Leser nur diese freundliche Entschuldigung sehen.
Das ist echtes Enterprise-Engineering.

Zusammenfassung: Zwei Welten, eine Architektur
Wir haben das Frontend unseres Headless CMS auf ein völlig neues Level gehoben. Mit der konsequenten Nutzung der Next.js App Router Architektur und dem strategischen Einsatz von Route Groups haben wir einen ehemals chaotischen Monolithen in zwei hochspezialisierte, isolierte Welten aufgeteilt.
Du hast gelernt, wie der unsichtbare Ordner (admin) unser geschütztes Redaktions-Dashboard kapselt und striktes, dynamisches serverseitiges Rendering (SSR) erzwingt, um Laravel Sanctum-Cookies bei jedem Request zu validieren. Gleichzeitig haben wir mit dem (public)-Ordner eine rasend schnelle Bühne für unsere Endnutzer geschaffen. Durch den Einsatz von ISR (Incremental Static Regeneration) und Caching fangen wir Tausende von Seitenaufrufen ab, ohne unsere Laravel-API zu belasten.
Wir haben dynamisches Routing ([slug]) gemeistert, die extrem mächtige Next.js Metadata-API für perfektes SEO eingebunden und unsere User Experience mit Streaming (loading.tsx) und intelligenten Fallbacks (error.tsx, not-found.tsx) auf Enterprise-Standard optimiert. Dein Frontend ist nun eine Festung und ein Rennwagen zugleich.
Teil der Serie
Headless CMS mit Laravel und Next.js
Headless CMS mit Laravel 12 & Next.js: Der ultimative Guide Pillar
Das perfekte Laravel Next.js Setup: Projektstruktur für dein Headless CMS
Datenbank-Design für ein skalierbares Headless CMS
Perfekte RESTful API mit Laravel 12 & API Resources bauen
Sichere Laravel Sanctum Next.js Authentifizierung bauen
Das ultimative Next.js Admin Dashboard mit shadcn/ui bauen
Rollen & Rechte-Management mit Laravel Spatie integrieren
Next.js App Router Architektur für Admin & Public
CRUD im Frontend: React Hook Form & Zod mit API
Datei-Uploads und Media-Management über die API
React Server Components für maximalen SEO-Boost
Häufig gestellte Fragen (FAQ)
Ausblick auf Teil 8: CRUD im Frontend: React Hook Form & Zod mit API
Unsere Architektur steht, die Datentabellen laden pfeilschnell und die Blog-Artikel sind für Google optimiert. Doch ein CMS ist wertlos, wenn Redakteure keine neuen Inhalte erstellen können!
Im nächsten Teil unserer Masterclass erwecken wir das Admin-Dashboard endlich voll zum Leben. Wir werden vollständige CRUD-Operationen (Create, Read, Update, Delete) direkt in unserem Next.js-Frontend implementieren. Du lernst, wie du hochkomplexe Formulare mit React Hook Form bändigst, Benutzereingaben in Echtzeit mit Zod strikt validierst und diese Datenpakete anschließend sicher über unsere Sanctum-Axios-Instanz an die Laravel-API sendest. Wir machen dein Frontend interaktiv!
Hier geht es zu Teil 8: CRUD im Frontend: React Hook Form & Zod mit API

Dietrich Bojko
Senior Webentwickler
Webinteger arbeitet seit vielen Jahren produktiv mit
Linux-basierten Entwicklungsumgebungen unter Windows.
Der Fokus liegt auf
performanten Setups mit WSL 2, Docker, PHP, Node.js und modernen
Build-Tools in realen Projekten –
nicht auf theoretischen Beispielkonfigurationen.
Die Artikel dieser Serie entstehen direkt aus dem täglichen Einsatz in Kunden- und Eigenprojekten und dokumentieren bewusst auch typische Fehler, Engpässe und bewährte Workarounds.


