Skip to content
kobiakov.dev
Zurück zum Blog

Next.js 15 in Produktion: App Router, PPR und Caching at Scale

Ein praktischer Leitfaden zum Aufbau einer Next.js-15-Anwendung für die Produktion — Server Components, Partial Prerendering, Cache-Invalidierung und Deployment-Muster.

Vladyslav Kobiakov2 Min. Lesezeit
Next.js 15 in Produktion: App Router, PPR und Caching at Scale

Next.js 15 bringt eine Reihe von Funktionen mit, die — richtig eingesetzt — Anwendungen ermöglichen, die gleichzeitig schnell laden, günstig auszuliefern und leicht zu warten sind. Falsch eingesetzt, erzeugen dieselben Funktionen Caching-Bugs, die in der Produktion schmerzhaft zu debuggen sind. Dieser Beitrag behandelt die Architekturentscheidungen, auf die es ankommt.

Das mentale Modell: Wo läuft der Code?

Bevor Sie eine einzige Zeile schreiben, beantworten Sie das für jede Komponente:

| Frage | Antwort → | |---|---| | Ändern sich diese Daten pro Request? | Server Component mit cache: 'no-store' | | Ändern sich diese Daten nach Zeitplan? | Server Component mit revalidate | | Ändern sich diese Daten nie? | Server Component, vollständig statisch | | Braucht das Interaktivität? | Client Component ('use client') |

Der Standard im App Router sind Server Components. 'use client' hinzuzufügen ist ein bewusstes Opt-in, nicht der Ausgangspunkt.

Ordnerstruktur für eine echte Anwendung

app/
  [locale]/
    (public)/          ← Layout mit Navbar/Footer
      page.tsx          ← Startseite, statisch
      blog/
        page.tsx        ← Übersicht, revalidate: 60
        [slug]/page.tsx ← Detail, statisch + ISR
      portfolio/
        page.tsx        ← statisch
    (protected)/       ← Layout mit Auth-Prüfung
      dashboard/
        page.tsx        ← dynamisch, kein Cache
  api/
    contact/route.ts   ← POST, rate-limited
    og/route.ts        ← dynamisches OG-Bild

Route-Gruppen (public) und (protected) teilen unterschiedliche Layouts, ohne die URL zu beeinflussen. Das ist sauberer, als jede geschützte Seite einzeln in eine Auth-Prüfung zu wickeln.

Server Components: Muster für das Datenholen

Paralleles Fetching mit Promise.all

Warten Sie nie seriell, wenn die Requests unabhängig sind:

// ❌ 600ms (zwei serielle Requests) const projects = await getProjects(); const testimonials = await getTestimonials(); // ✅ 300ms (parallel) const [projects, testimonials] = await Promise.all([ getProjects(), getTestimonials(), ]);
ts

Request-Deduplizierung mit React cache

Wenn mehrere Komponenten auf derselben Seite dieselben Daten brauchen, sorgt React.cache dafür, dass das Fetch nur einmal pro Render läuft:

import { cache } from 'react'; export const getUser = cache(async (id: string) => { return prisma.user.findUnique({ where: { id } }); });
ts

Sowohl <Header> als auch <Dashboard> können getUser(id) aufrufen — die Datenbankabfrage wird nur einmal ausgeführt.

Partial Prerendering (PPR)

PPR ist die bedeutendste Architekturänderung in Next.js 15. Sie erlaubt es, eine einzelne Route teilweise statisch zu machen:

// next.config.ts export default { experimental: { ppr: 'incremental', }, }; // app/[locale]/page.tsx export const experimental_ppr = true; export default function HomePage() { return ( <> <HeroSection /> {/* statische Hülle — sofort vom CDN */} <Suspense fallback={<ProjectsSkeleton />}> <FeaturedProjects /> {/* dynamisch — nach der Hülle gestreamt */} </Suspense> </> ); }
ts

Die HTML-Hülle mit <HeroSection> wird am Edge gecacht. Die dynamischen Teile streamen hinter einer Suspense-Grenze nach. Der Nutzer sieht Inhalt in unter 100 ms; personalisierte Daten kommen kurz danach.

Strategie zur Cache-Invalidierung

Next.js 15 hat drei Cache-Ebenen. Wissen Sie, welche Sie invalidieren:

import { revalidatePath, revalidateTag } from 'next/cache'; // Eine bestimmte URL invalidieren (z. B. nach Veröffentlichung eines Blogposts) await revalidatePath('/blog/[slug]', 'page'); // Alle mit 'projects' getaggten Seiten invalidieren (bevorzugt für datengetriebene Invalidierung) await revalidateTag('projects'); // Ein Fetch taggen, damit es per Tag invalidiert werden kann const data = await fetch('/api/projects', { next: { tags: ['projects'], revalidate: 300 }, });
ts

Der revalidateTag-Ansatz skaliert besser als revalidatePath, weil Sie Daten auf Fetch-Ebene taggen, nicht auf Routen-Ebene. Ein API-Aufruf invalidiert jede Seite, die diese Daten verwendet hat.

Middleware für i18n und Auth

Middleware läuft am Edge, bevor irgendeine Seite rendert. Halten Sie sie minimal — sie läuft bei jedem Request:

// middleware.ts import { NextRequest, NextResponse } from 'next/server'; import createIntlMiddleware from 'next-intl/middleware'; import { routing } from '@/i18n/routing'; const intlMiddleware = createIntlMiddleware(routing); export function middleware(request: NextRequest) { // Auth-Prüfung für geschützte Routen const isProtected = request.nextUrl.pathname.startsWith('/dashboard'); if (isProtected) { const session = request.cookies.get('next-auth.session-token'); if (!session) { return NextResponse.redirect(new URL('/login', request.url)); } } return intlMiddleware(request); }
ts

Vermeiden Sie Datenbankaufrufe in der Middleware. Prüfen Sie nur Cookies oder JWT-Claims — alles Schwerere gehört in die Server Component der Seite.

Checkliste fürs Produktions-Deployment

Vor dem Push zu Vercel:

# 1. Type-Check npx tsc --noEmit # 2. Lint npm run lint # 3. Lokaler Build, um Fehler in der statischen Generierung zu finden npm run build # 4. Bundle-Größe prüfen ANALYZE=true npm run build
bash

Achten Sie auf:

  • Server Components, die versehentlich client-only Pakete importieren (window, localStorage)
  • cookies() oder headers() außerhalb eines dynamischen Kontexts aufgerufen — das nimmt die gesamte Route aus der statischen Generierung
  • Bilder ohne explizite width/height, die Layout-Shift verursachen

Fazit

Die Stärke des App Routers entsteht aus dem Verständnis der Grenze zwischen statisch und dynamisch — und daraus, sie bewusst zu setzen. Standardmäßig statisch, Suspense-Grenzen für dynamische Abschnitte, getaggte Fetches für granulare Invalidierung und eine schlanke Middleware. Diese vier Entscheidungen decken 90 % der Performance-Probleme in der Produktion ab, bevor sie entstehen.

Share: