🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Next.js 15 Vollständiger Leitfaden 2026: App Router, Serverkomponenten und PPR

⏱️5 min read  ·  983 words

Next.js 15 festigt die Full-Stack-Zukunft von React. Mit dem stabilen App Router, React Server Components als Standard und Turbopack als Ersatz für Webpack ist die Erstellung von Next.js-Produktionsanwendungen im Jahr 2026 schneller und leistungsfähiger als je zuvor.

Was ist neu in Next.js 15?

  • Turbopack stabil– 10x schnellere Builds, 700x schnelleres HMR im Vergleich zu Webpack
  • React 19 erstklassiger Support– Serverkomponenten, Aktionen, use()-Hook
  • Partielles Vorrendering (PPR)– statische Shell + dynamische Inseln auf derselben Seite
  • Verbessertes Caching– kein überraschendes Cache-by-Default-Verhalten mehr
  • Dynamische APIs— Cookies(), Headers() und SearchParams sind jetzt asynchron

Projekt-Setup

# Create new Next.js 15 project
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack

cd my-app

# Project structure
# app/
#   layout.tsx          — root layout
#   page.tsx            — home page
#   globals.css
#   (dashboard)/        — route group (no URL segment)
#     page.tsx
#   blog/
#     [slug]/
#       page.tsx        — dynamic route
#   api/
#     users/
#       route.ts        — API route handler

Grundlagen des App-Routers

// app/layout.tsx — Root Layout (required)
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: { template: "%s | My App", default: "My App" },
  description: "Built with Next.js 15",
  openGraph: {
    type: "website",
    locale: "en_US",
    url: "https://myapp.com",
    siteName: "My App",
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Serverkomponenten vs. Clientkomponenten

// Server Component (default) — runs on server, no JavaScript sent to client
// app/blog/page.tsx
async function BlogPage() {
  // Direct database/API access — no useEffect needed
  const posts = await db.query("SELECT * FROM posts ORDER BY created_at DESC");

  return (
    <main>
      <h1>Blog</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </main>
  );
}

// Client Component — add "use client" directive
"use client";
import { useState } from "react";

function SearchBar({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState("");

  return (
    <input
      value={query}
      onChange={e => setQuery(e.target.value)}
      onKeyDown={e => e.key === "Enter" && onSearch(query)}
      placeholder="Search..."
    />
  );
}

Datenabruf mit Serverkomponenten

// Parallel data fetching — no waterfalls
async function Dashboard() {
  // These run in parallel
  const [user, posts, stats] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchStats(),
  ]);

  return (
    <div>
      <UserCard user={user} />
      <PostList posts={posts} />
      <StatsPanel stats={stats} />
    </div>
  );
}

// Dynamic rendering with searchParams (Next.js 15: async)
export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ q?: string; page?: string }>;
}) {
  const { q = "", page = "1" } = await searchParams;
  const results = await searchPosts(q, parseInt(page));

  return <SearchResults results={results} query={q} />;
}

Serveraktionen

Mit Serveraktionen können Sie Servercode aus Formularen und Ereignishandlern ausführen:

// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;
  const content = formData.get("content") as string;

  // Validate
  if (!title || title.length < 3) {
    return { error: "Title must be at least 3 characters" };
  }

  // Save to database
  await db.posts.create({ title, content });

  // Revalidate and redirect
  revalidatePath("/blog");
  redirect("/blog");
}

// Use in a form
export default function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Title" required />
      <textarea name="content" placeholder="Content" />
      <button type="submit">Publish</button>
    </form>
  );
}

Routenhandler (API-Routen)

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const page = parseInt(searchParams.get("page") ?? "1");

  const users = await db.users.findMany({
    skip: (page - 1) * 10,
    take: 10,
  });

  return NextResponse.json({ users, page });
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const user = await db.users.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}

// app/api/users/[id]/route.ts
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  await db.users.delete({ where: { id } });
  return new NextResponse(null, { status: 204 });
}

Partielles Vorrendering (PPR)

Mit PPR können Sie statische und dynamische Inhalte auf einer einzigen Seite kombinieren:

// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  experimental: {
    ppr: true,
  },
};

export default config;

// page.tsx — static shell + dynamic island
import { Suspense } from "react";

export default function ProductPage({ params }: { params: { id: string } }) {
  return (
    <div>
      {/* Static — generated at build time */}
      <ProductDetails id={params.id} />

      {/* Dynamic — rendered per request */}
      <Suspense fallback={<PriceSkeleton />}>
        <DynamicPrice id={params.id} />
      </Suspense>

      <Suspense fallback={<ReviewsSkeleton />}>
        <UserReviews id={params.id} />
      </Suspense>
    </div>
  );
}

Middleware

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token")?.value;
  const isProtected = request.nextUrl.pathname.startsWith("/dashboard");

  if (isProtected && !token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  // Add security headers
  const response = NextResponse.next();
  response.headers.set("X-Frame-Options", "DENY");
  response.headers.set("X-Content-Type-Options", "nosniff");

  return response;
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

Bildoptimierung

import Image from "next/image";

// Optimized image — auto WebP/AVIF, lazy loading, size hints
export function HeroImage({ src, alt }: { src: string; alt: string }) {
  return (
    <Image
      src={src}
      alt={alt}
      width={1200}
      height={630}
      priority // LCP image — eager load
      quality={85}
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j..." // low-quality placeholder
    />
  );
}

// Fill container
<div style={{ position: "relative", aspectRatio: "16/9" }}>
  <Image src="/hero.jpg" alt="Hero" fill sizes="100vw" />
</div>

Bereitstellung und Produktion

  • Vercel– Zero-Config, Edge-Netzwerk, automatisches PPR
  • Selbst gehostetnext build && next startmit Node.js
  • Docker– offizielle Next.js Docker-Datei mit eigenständiger Ausgabe

Next.js 15 ist das umfassendste React-Framework für 2026. Serverkomponenten verhindern Overfetching, Serveraktionen vereinfachen Mutationen und PPR liefert das Beste aus statischem und dynamischem Rendering auf derselben Seite.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা