Menu
Zurück zum Blog
2 min read
Web Development

Next.js 15 Deep Dive: React Server Components und Streaming in Production

Umfassende Analyse von Next.js 15 Features. React Server Components, Turbopack, Server Actions und Performance-Optimierung für 2026.

Next.js 15React Server ComponentsRSCTurbopackServer ActionsApp Router
Next.js 15 Deep Dive: React Server Components und Streaming in Production

Next.js 15 Deep Dive: React Server Components und Streaming in Production

Meta-Description: Umfassende Analyse von Next.js 15 Features. React Server Components, Turbopack, Server Actions und Performance-Optimierung für 2026.

Keywords: Next.js 15, React Server Components, RSC, Turbopack, Server Actions, App Router, Streaming SSR, React 19


Einführung

Next.js 15 ist nicht nur ein Update – es ist ein Paradigmenwechsel. Server-first, Streaming-enabled und mit React 19 als Basis definiert es Full-Stack React 2026 neu.

"2026 ist Next.js nicht mehr nur für Rendering – es ist für skalierbare, server-first, streaming-enabled Applications."

Die wichtigsten Features

┌─────────────────────────────────────────────────────────────┐
│                    NEXT.JS 15 HIGHLIGHTS                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  🚀 Turbopack (Stable)                                     │
│     └── 10x schneller als Webpack                          │
│                                                             │
│  ⚛️ React 19 Support                                        │
│     └── Server Components, Actions, Compiler               │
│                                                             │
│  🔄 Async Request APIs                                      │
│     └── cookies(), headers(), params() sind async          │
│                                                             │
│  📦 Caching Changes                                         │
│     └── fetch, GET Routes nicht mehr default cached        │
│                                                             │
│  ⚡ Partial Prerendering (Experimental)                     │
│     └── Static Shell + Dynamic Streaming                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

React Server Components (RSC)

Das Konzept

// Server Component (Default in App Router)
// Kein 'use client' = Server Component
async function ProductPage({ params }: { params: { id: string } }) {
  // Direkt DB-Zugriff - kein API nötig!
  const product = await prisma.product.findUnique({
    where: { id: params.id }
  });

  // Dieses JavaScript wird NICHT an den Client gesendet
  const analytics = calculateComplexAnalytics(product);

  return (
    <div>
      <h1>{product.name}</h1>
      <ProductDetails product={product} />
      {/* Client Component für Interaktivität */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}

Server vs. Client Components

// ❌ Don't: Alles als Client Component
'use client';
export function ProductList() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    fetch('/api/products').then(r => r.json()).then(setProducts);
  }, []);

  return products.map(p => <Product key={p.id} product={p} />);
}

// ✅ Do: Server Component mit Client Component für Interaktivität
// Server Component (app/products/page.tsx)
async function ProductsPage() {
  const products = await prisma.product.findMany();

  return (
    <div>
      {products.map(p => (
        <ProductCard key={p.id} product={p}>
          {/* Client Component nur wo nötig */}
          <FavoriteButton productId={p.id} />
        </ProductCard>
      ))}
    </div>
  );
}

// Client Component (components/FavoriteButton.tsx)
'use client';
export function FavoriteButton({ productId }: { productId: string }) {
  const [isFavorite, setIsFavorite] = useState(false);

  return (
    <button onClick={() => setIsFavorite(!isFavorite)}>
      {isFavorite ? '❤️' : '🤍'}
    </button>
  );
}

Server Actions

Form Handling ohne API Route

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createProduct(formData: FormData) {
  const name = formData.get('name') as string;
  const price = parseFloat(formData.get('price') as string);

  // Validation
  if (!name || !price) {
    return { error: 'Name und Preis erforderlich' };
  }

  // Direkt DB-Operation
  const product = await prisma.product.create({
    data: { name, price }
  });

  // Cache invalidieren
  revalidatePath('/products');

  // Redirect
  redirect(`/products/${product.id}`);
}
// app/products/new/page.tsx
import { createProduct } from '../actions';

export default function NewProductPage() {
  return (
    <form action={createProduct}>
      <input name="name" placeholder="Produktname" required />
      <input name="price" type="number" step="0.01" required />
      <button type="submit">Erstellen</button>
    </form>
  );
}

Server Actions mit useActionState (React 19)

'use client';

import { useActionState } from 'react';
import { createProduct } from '../actions';

export function ProductForm() {
  const [state, formAction, isPending] = useActionState(
    createProduct,
    { error: null }
  );

  return (
    <form action={formAction}>
      {state.error && (
        <div className="text-red-500">{state.error}</div>
      )}

      <input name="name" placeholder="Produktname" required />
      <input name="price" type="number" step="0.01" required />

      <button type="submit" disabled={isPending}>
        {isPending ? 'Speichern...' : 'Erstellen'}
      </button>
    </form>
  );
}

Streaming SSR

Suspense für Progressive Loading

// app/dashboard/page.tsx
import { Suspense } from 'react';

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Schneller Content zuerst */}
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />
      </Suspense>

      {/* Langsamer Content streamt nach */}
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders />
      </Suspense>
    </div>
  );
}

// Diese Components können unabhängig laden
async function Stats() {
  const stats = await fetchStats(); // 100ms
  return <StatsDisplay data={stats} />;
}

async function RevenueChart() {
  const data = await fetchRevenueData(); // 500ms
  return <Chart data={data} />;
}

async function RecentOrders() {
  const orders = await fetchOrders(); // 300ms
  return <OrdersTable orders={orders} />;
}

Loading UI

// app/dashboard/loading.tsx
export default function DashboardLoading() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
      <div className="grid grid-cols-4 gap-4 mb-8">
        {[...Array(4)].map((_, i) => (
          <div key={i} className="h-24 bg-gray-200 rounded" />
        ))}
      </div>
      <div className="h-64 bg-gray-200 rounded" />
    </div>
  );
}

Async Request APIs (Breaking Change)

// Next.js 14
import { cookies, headers } from 'next/headers';

export default function Page() {
  const cookieStore = cookies(); // Synchron
  const headersList = headers(); // Synchron
  return <div>...</div>;
}

// Next.js 15
import { cookies, headers } from 'next/headers';

export default async function Page() {
  const cookieStore = await cookies(); // Async!
  const headersList = await headers(); // Async!
  return <div>...</div>;
}

Migration Codemod

npx @next/codemod@canary upgrade latest

Caching Changes

Neues Default-Verhalten

// Next.js 14: Gecached by default
const data = await fetch('https://api.example.com/data');

// Next.js 15: NICHT gecached by default
const data = await fetch('https://api.example.com/data');

// Explizit cachen
const cachedData = await fetch('https://api.example.com/data', {
  cache: 'force-cache' // oder next: { revalidate: 3600 }
});

Route Handler Caching

// app/api/data/route.ts

// Next.js 15: GET ist NICHT mehr default cached
export async function GET() {
  const data = await fetchData();
  return Response.json(data);
}

// Explizit cachen
export const dynamic = 'force-static';
export async function GET() {
  // ...
}

Turbopack in Production

Aktivierung

# next.config.js ist nicht nötig für Dev
next dev --turbopack

# Für Build (experimentell)
TURBOPACK=1 next build

Performance-Vergleich

MetrikWebpackTurbopackVerbesserung
**Cold Start**8.5s1.2s7x schneller
**HMR**500ms50ms10x schneller
**Full Rebuild**45s8s5.6x schneller

Performance Best Practices

1. Component Composition

// ❌ Großes Client Component
'use client';
export function Dashboard() {
  const [data, setData] = useState(null);
  // Viel Logik...
  return <div>...</div>;
}

// ✅ Server Component mit kleinen Client Components
export async function Dashboard() {
  const data = await fetchData();

  return (
    <div>
      <Header data={data.header} />
      <Sidebar items={data.menuItems} />
      <Content>
        <InteractiveChart data={data.chartData} /> {/* Client */}
        <FilterPanel /> {/* Client */}
      </Content>
    </div>
  );
}

2. Data Fetching Patterns

// ❌ Sequential Fetching
async function Page() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments(posts.map(p => p.id));
  return <div>...</div>;
}

// ✅ Parallel Fetching
async function Page() {
  const user = await fetchUser();

  // Parallel fetchen
  const [posts, friends] = await Promise.all([
    fetchPosts(user.id),
    fetchFriends(user.id)
  ]);

  return <div>...</div>;
}

3. Streaming Optimization

// Optimale Streaming-Architektur
export default function ProductPage({ params }) {
  return (
    <div>
      {/* Sofort gerendert */}
      <Header />

      {/* Schnelle Daten zuerst */}
      <Suspense fallback={<ProductSkeleton />}>
        <ProductInfo id={params.id} />
      </Suspense>

      {/* Langsame Daten später */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews id={params.id} />
      </Suspense>

      <Suspense fallback={<RecommendationsSkeleton />}>
        <Recommendations id={params.id} />
      </Suspense>
    </div>
  );
}

Partial Prerendering (Experimental)

// next.config.js
module.exports = {
  experimental: {
    ppr: true
  }
};

// app/page.tsx
import { Suspense } from 'react';

export default function HomePage() {
  return (
    <div>
      {/* Statisch prerendered */}
      <Header />
      <Hero />

      {/* Dynamisch gestreamt */}
      <Suspense fallback={<ProductsSkeleton />}>
        <PersonalizedProducts />
      </Suspense>

      {/* Statisch */}
      <Footer />
    </div>
  );
}

Security Notes

Zwei kritische CVEs wurden für Next.js gemeldet:

  • CVE-2025-55184: DoS via Server Components
  • CVE-2025-55183: Source Code Exposure

Sofortiges Update auf die neueste Version empfohlen!


Fazit

Next.js 15 verändert wie wir React-Anwendungen bauen:

  1. Server-First: RSC als Default reduziert Client-Bundle
  2. Streaming: Bessere Time-to-First-Byte
  3. Server Actions: Keine API-Routes mehr für Forms
  4. Turbopack: Dramatisch schnellere Builds

2026 ist Next.js die Plattform für moderne Full-Stack React.


Bildprompts

  1. "React components streaming from server to client, data flow visualization, modern web architecture"
  2. "Next.js logo with speed lines, turbo boost effect, performance concept"
  3. "Server and client components merging, hybrid rendering visualization, clean tech diagram"

Quellen