Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
512 lines
12 KiB
Markdown
512 lines
12 KiB
Markdown
# 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
|
||
|
||
```tsx
|
||
// 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
|
||
|
||
```tsx
|
||
// ❌ 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
|
||
|
||
```tsx
|
||
// 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}`);
|
||
}
|
||
```
|
||
|
||
```tsx
|
||
// 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)
|
||
|
||
```tsx
|
||
'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
|
||
|
||
```tsx
|
||
// 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
|
||
|
||
```tsx
|
||
// 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)
|
||
|
||
```tsx
|
||
// 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
|
||
|
||
```bash
|
||
npx @next/codemod@canary upgrade latest
|
||
```
|
||
|
||
---
|
||
|
||
## Caching Changes
|
||
|
||
### Neues Default-Verhalten
|
||
|
||
```tsx
|
||
// 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
|
||
|
||
```tsx
|
||
// 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
|
||
|
||
```bash
|
||
# next.config.js ist nicht nötig für Dev
|
||
next dev --turbopack
|
||
|
||
# Für Build (experimentell)
|
||
TURBOPACK=1 next build
|
||
```
|
||
|
||
### Performance-Vergleich
|
||
|
||
| Metrik | Webpack | Turbopack | Verbesserung |
|
||
|--------|---------|-----------|--------------|
|
||
| **Cold Start** | 8.5s | 1.2s | 7x schneller |
|
||
| **HMR** | 500ms | 50ms | 10x schneller |
|
||
| **Full Rebuild** | 45s | 8s | 5.6x schneller |
|
||
|
||
---
|
||
|
||
## Performance Best Practices
|
||
|
||
### 1. Component Composition
|
||
|
||
```tsx
|
||
// ❌ 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
|
||
|
||
```tsx
|
||
// ❌ 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
|
||
|
||
```tsx
|
||
// 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)
|
||
|
||
```tsx
|
||
// 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
|
||
|
||
- [Next.js 15 Release Blog](https://nextjs.org/blog/next-15)
|
||
- [Next.js Documentation](https://nextjs.org/docs)
|
||
- [React Server Components](https://nextjs.org/docs/app/getting-started/server-and-client-components)
|
||
- [Medium: Next.js 15 Deep Dive](https://medium.com/@EnaModernCoder/next-js-15-deep-dive-the-hidden-power-behind-the-latest-evolution-of-react-frameworks-e792e3e6e3ae)
|