Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe

Einleitung
Next.js 15 ist noch frisch – ich habe die Docs mehrfach gelesen und Tutorials geschaut, blieb aber bei Theorie stehen. Server Actions, App Router – alles klar im Kopf, aber in einem echten Projekt? Am Wochenende habe ich aufgehört nur zu schauen und ein Full-Stack-Blog gebaut: Frontend, Backend, Datenbank, Deploy. Zwei Tage später lief das Blog auf Vercel, Lighthouse Performance 96.
Dieser Artikel ist kein Copy-Paste-Tutorial, sondern das Warum, Fallstricke und Entscheidungen. Stack: Next.js 15 + Server Actions + Prisma + PostgreSQL – produktionsreifer Code.
Warum Next.js 15?
Bei der Stack-Wahl habe ich lange gezögert.
Als Next.js 15 kam, las ich in der Community: „Schon wieder ein Update, ich kann nicht mehr.“ Ehrlich: etwas Widerstand – Version 14 saß noch nicht fest. Nach den neuen Features war klar: das Upgrade lohnt sich.
Server Actions: Schluss mit umständlichen API Routes
Beim Full-Stack-Development nervte mich am meisten das Schreiben von API Routes. api/posts/route.ts, POST definieren, Body parsen, Response zurück – immer dasselbe.
Server Actions ändern das Spiel. 'use server' vor die Funktion, in der Komponente aufrufen – fertig. Beim ersten Mal dachte ich: „Backend-Code in der Frontend-Datei?“ Einmal ausprobiert – überzeugt.
Früher für einen Blog-Post:
// Alte Methode: API Route nötig
// app/api/posts/route.ts
export async function POST(request: Request) {
const body = await request.json()
// viel Logik...
}
// Frontend: fetch
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(data)
})
Heute:
// app/actions/post-actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
// Direkt in die DB – angenehm
return await prisma.post.create({
data: { title, content }
})
}
In der Komponente wie eine lokale Funktion. Bildlich: früher selbst zum Restaurant (API Routes), heute Lieferservice (Server Actions).
Turbopack: Entwicklung deutlich schneller
Next.js 15 macht Turbopack stabil. Offiziell: 76,7 % schnellerer Start, 96,3 % schnellere Updates – klang nach Marketing, in der Praxis stimmt es.
Über 30 Komponenten: Webpack 7–8 Sekunden Start, Turbopack unter 2. Hot Reload fast instant – ein großer Unterschied für die tägliche Arbeit.
Warum dieser Stack für ein Blog?
Drei Gründe für Next.js als Blog-Basis:
SSR/SSG von Haus aus – SEO zählt. Server-Rendering und statische Generierung sind dafür gemacht. Google bekommt vollständiges HTML – deutlich besser als reines Client-Rendering.
Prisma-Typsicherheit – Mit Mongoose für MongoDB Typen manuell pflegen, schnell Bugs. Prisma generiert TypeScript aus dem Schema – IntelliSense, weniger Fehler.
Server Actions vereinfachen – Keine API Routes, mindestens 30 % weniger Code. Klassisch wären mehrere route.ts-Dateien nötig gewesen.
Tech-Stack und Architektur
Nach dem Warum kommt das Wie.
Mein vollständiger Stack
- Frontend: Next.js 15 + TypeScript + Tailwind CSS
- Backend: Next.js Server Actions + NextAuth.js
- Datenbank: PostgreSQL + Prisma ORM
- Deploy: Vercel
Warum nicht MongoDB? Am Anfang dachte ich daran – flexibler. Prisma unterstützt PostgreSQL besser; für Artikel, Nutzer, Kommentare passt relational oft besser.
Verzeichnisstruktur
my-blog/
├── app/
│ ├── (auth)/ # Auth-Seiten
│ ├── blog/ # Blog-Seiten
│ │ └── [slug]/ # Dynamische Route
│ ├── dashboard/ # Dashboard
│ ├── actions/ # Server Actions zentral
│ └── api/auth/ # NextAuth
├── components/ # Wiederverwendbare Komponenten
├── lib/
│ ├── prisma.ts # Prisma-Singleton (wichtig!)
│ └── utils.ts # Hilfsfunktionen
├── prisma/
│ └── schema.prisma # DB-Schema
└── public/ # Statische Assets
Mehrere Iterationen. Anfangs Server Actions in Page-Dateien verstreut – unübersichtlich. Ein actions-Ordner macht alles klarer.
Datenbank-Schema
Hier habe ich einen großen Fehler gemacht. Version 1: zu komplex – Tags, Kategorien, Lese-Statistiken – am Ende ungenutzt, halber Tag zum Vereinfachen.
Finales Kern-Schema:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
image String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
slug String @unique
content String @db.Text
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slug])
@@index([authorId])
}
Die beiden @@index sind Performance-kritisch. Listen nach slug, Profile nach authorId – mit Index deutlich schneller.
Kernfunktionen im Detail
Architektur steht – jetzt Code.
3.1 Umgebung und Initialisierung
Next.js 15-Projekt anlegen:
npx create-next-app@latest my-blog
cd my-blog
npm install prisma @prisma/client zod next-auth
Prisma initialisieren:
npx prisma init
Erzeugt prisma/schema.prisma und .env. PostgreSQL-Verbindung:
DATABASE_URL="postgresql://username:password@localhost:5432/myblog?schema=public"
Lokal PostgreSQL per Docker:
docker run --name blog-postgres -e POSTGRES_PASSWORD=mypassword -p 5432:5432 -d postgres
3.2 Prisma-Integration
Nach dem Schema Migration:
npx prisma migrate dev --name init
Wichtiger Stolperstein: Prisma-Client-Singleton. Beim ersten Vercel-Deploy vergessen – nach Stunden „Too many connections“, kurz Panik wegen vermeintlichem Angriff.
Ursache: Hot Reload erzeugt neue Prisma-Clients, Connection Pool explodiert. Lösung:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Wirkt komplex, garantiert aber eine Instanz in der Entwicklung. Offizielle Prisma-Best Practice.
3.3 Server Actions in der Praxis
Artikel anlegen – Kernfeature:
// app/actions/post-actions.ts
'use server'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { z } from 'zod'
// Zod-Validierung für Typsicherheit
const PostSchema = z.object({
title: z.string().min(1, 'Titel darf nicht leer sein').max(100),
content: z.string().min(10, 'Inhalt mindestens 10 Zeichen'),
slug: z.string().regex(/^[a-z0-9-]+$/, 'Slug: nur Kleinbuchstaben, Ziffern, Bindestriche')
})
export async function createPost(formData: FormData) {
const validatedFields = PostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
slug: formData.get('slug')
})
if (!validatedFields.success) {
return { error: 'Validierung fehlgeschlagen' }
}
try {
const post = await prisma.post.create({
data: {
...validatedFields.data,
authorId: 'user-id-here' // In Produktion aus Session
}
})
revalidatePath('/blog')
return { success: true, post }
} catch (error) {
return { error: 'Artikel konnte nicht erstellt werden' }
}
}
In der Komponente:
// app/dashboard/new-post/page.tsx
import { createPost } from '@/app/actions/post-actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" placeholder="Titel" />
<textarea name="content" placeholder="Inhalt" />
<input name="slug" placeholder="URL-Slug" />
<button type="submit">Veröffentlichen</button>
</form>
)
}
Kein useState, kein fetch, kein onSubmit. Das Formular ruft die Server Action auf – Next.js übernimmt Serialisierung, Request und Fehler.
Unterschied zu API Routes: Server Actions = Lieferservice, API Routes = selbst abholen. Meist reichen Server Actions.
3.4 Authentifizierung
NextAuth.js – überschaubar:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHubProvider from 'next-auth/providers/github'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!
})
]
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
GitHub OAuth: App in GitHub Developer Settings, Client ID und Secret in .env. Ca. 10 Minuten – deutlich weniger Aufwand als eigenes JWT.
3.5 SSR/SSG-Optimierung
Performance-Kern. Anfangs alles SSR – jeder Listenaufruf hit die DB, unnötig. Dann Mix-Strategie:
Blog-Liste – SSG + ISR:
// app/blog/page.tsx
import { prisma } from '@/lib/prisma'
export const revalidate = 3600
export default async function BlogList() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' }
})
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content.slice(0, 150)}...</p>
</article>
))}
</div>
)
}
Statisches HTML beim Build, stündlich Regenerierung – Besucher bekommen schnelle statische Dateien.
Artikel-Detail – dynamisches SSR:
// app/blog/[slug]/page.tsx
export default async function Post({ params }: { params: { slug: string } }) {
const post = await prisma.post.findUnique({
where: { slug: params.slug }
})
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
Jeder Aufruf: aktuellster Inhalt aus der DB.
Performance-Vergleich:
Der Unterschied ist klar – deshalb eignet sich Next.js gut für Blogs: SEO und Performance.
Deploy und Optimierung
Code fertig – Deploy.
Vercel-Workflow
Code nach GitHub, Repository in Vercel importieren. Next.js wird erkannt, fast alles automatisch. Manuell: Umgebungsvariablen:
DATABASE_URL="Produktions-DB-URL"
GITHUB_ID="OAuth Client ID"
GITHUB_SECRET="OAuth Secret"
NEXTAUTH_URL="https://your-domain.vercel.app"
NEXTAUTH_SECRET="Zufallsstring"
Produktions-DB: Supabase Free PostgreSQL, 500 MB/Monat – für persönliche Blogs genug.
Deploy – ca. 2 Minuten live. Beim ersten Aufruf der eigenen Domain kurz innehalten – Screenshot wert.
Produktions-Optimierung
Deploy ist nicht das Ende:
Prisma Connection Pool:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
Serverless: jeder Aufruf kann neue Verbindung öffnen – directUrl nutzt den Pool.
Bilder:
import Image from 'next/image'
<Image
src="/avatar.jpg"
alt="Nutzer-Avatar"
width={100}
height={100}
/>
Next.js Image: WebP, Lazy Loading – spürbar schneller.
Finale Lighthouse-Werte:
Lighthouse: Desktop 96, Mobile 92, SEO 100. Zwei Tage von null auf live mit produktionsreifer Performance – das motiviert.
Erweiterungen und Lernressourcen
Aktuell MVP – mögliche nächste Schritte:
- Markdown-Editor: react-md-editor
- Kommentare: Giscus (GitHub Discussions)
- Suche: Algolia oder Prisma-Volltextsuche
- Dark Mode: mit next-themes einfach
Als Nächstes plane ich einen Markdown-Editor – Artikel schreiben wird angenehmer.
Empfohlene Ressourcen:
- Next.js-Dokumentation – maßgeblich
- Prisma-Dokumentation – Getting Started reicht oft
- Vollständiger Projektcode – zum Klonen und Ausprobieren
Einmal selbst bauen schlägt zehnmal Docs lesen. Theorie hilft – Praxis festigt.
Fazit
Aus dem Wochenende:
- Server Actions: Ersetzen die meisten API Routes – spürbar schnellere Entwicklung
- Prisma-Typsicherheit: Volles TypeScript – sicheres Coden
- SSG/SSR-Flexibilität: Rendering passend zur Seite – Performance und SEO
- Vercel-Deploy: Von Code zu live in unter 5 Minuten
Der größte Gewinn war nicht nur Technik, sondern Vertrauen – Full-Stack ist machbar, wenn man anfängt.
Wenn Sie ein eigenes Blog bauen wollen: jetzt starten. Probleme sind normal – ich habe neben der Doku unzählige Stolpersteine überwunden. Wenn Ihr Projekt live geht, lohnt sich jeder Schritt.
Hoffentlich sparen Sie ein paar Umwege. Fragen gerne in den Kommentaren – ich antworte, wenn möglich.
Ich freue mich auf Ihre Projekte!
Produktionsreifes Blog-System mit Next.js 15
Vom Setup bis Deploy: Server Actions, Prisma, SSG/SSR-Optimierung und Kernfunktionen
Estimated time: PT16H
-
1
Step 1: Umgebung und Projekt-Setup
Next.js 15-Projekt anlegen: -
2
Step 2: Prisma-Schema konfigurieren
Kern-Schema entwerfen: -
3
Step 3: Server Actions implementieren
Server-Actions-Datei: -
4
Step 4: Authentifizierung
NextAuth.js: -
5
Step 5: SSR/SSG-Strategie
Blog-Liste SSG+ISR: app/blog/page.tsx mit export const revalidate = 3600, prisma.post.findMany für veröffentlichte Posts, sortiert nach createdAt. Statisches HTML beim Build, stündliche Regenerierung, ~200 ms Ladezeit. Artikel-Detail dynamisches SSR: app/blog/[slug]/page.tsx, prisma.post.findUnique({ where: { slug: params.slug } }), ~800 ms, für häufig aktualisierte Inhalte. Vergleich: SSG ~200 ms, SSR ~800 ms, CSR ~1500 ms. -
6
Step 6: Vercel-Deploy und Optimierung
Code nach GitHub pushen. Projekt in Vercel importieren – Next.js automatisch erkannt. Umgebungsvariablen: DATABASE_URL, GITHUB_ID, GITHUB_SECRET, NEXTAUTH_URL (https://your-domain.vercel.app), NEXTAUTH_SECRET. Empfehlung: Supabase Free PostgreSQL (500 MB/Monat). Prisma: directUrl = env(“DIRECT_URL”) in schema.prisma für Serverless-Pool. Bilder: Next.js Image-Komponente, WebP, Lazy Loading. Deploy ~2 Minuten – Lighthouse Desktop 96, Mobile 92, SEO 100.
FAQ
Was ist der Unterschied zwischen Server Actions und API Routes in Next.js 15? Wann nutze ich welche?
Vorteile von Server Actions:
1) Bis zu 30 % weniger Code – keine API Routes nötig
2) Direkt in der Komponente aufrufbar – kein useState, fetch oder onSubmit-Handler
3) Next.js übernimmt Serialisierung, Netzwerk-Requests und Fehlerbehandlung
4) Typsicher – TypeScript-Typen direkt nutzbar
Beispiel: Vor der Funktion 'use server' setzen, dann in der Komponente aufrufen (<form action={createPost}>).
Vorteile von API Routes:
• Flexibler bei komplexer Request-Logik
• Middleware-Unterstützung
• Geeignet für benutzerdefinierte HTTP-Antworten
In den meisten Fällen reichen Server Actions – API Routes nur bei komplexer HTTP-Verarbeitung.
Wie groß ist der Performance-Gewinn von Turbopack? Wie fühlt es sich in der Praxis an?
Offizielle Daten:
• 76,7 % schnellerer lokaler Server-Start
• 96,3 % schnellere Code-Updates
Praxis-Erfahrung:
• Mein Projekt hat über 30 Komponenten – mit Webpack dauerte der Start 7–8 Sekunden, mit Turbopack unter 2 Sekunden
• Code-Änderungen: Hot Reload fast sofort
• Der Unterschied für die Developer Experience ist enorm
Turbopack ist in Rust geschrieben und deutlich schneller als Webpack – besonders in großen Projekten. Wenn Sie noch Webpack nutzen: Upgrade auf Next.js 15 und Turbopack ausprobieren.
Warum ist das Prisma-Client-Singleton wichtig? Was passiert ohne Konfiguration?
Problem:
• In der Next.js-Entwicklung triggert Hot Reload neue Prisma-Client-Instanzen
• Der Connection Pool läuft schnell voll → „Too many connections“
• Beim ersten Vercel-Deploy habe ich das vergessen – nach ein paar Stunden der Fehler, ich dachte kurz an einen DDoS-Angriff
Richtige Konfiguration:
• Globales Singleton in lib/prisma.ts:
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
So gibt es in der Entwicklung nur eine Prisma-Instanz; Produktion bleibt unverändert. Merken: offizielle Prisma-Best Practice!
Was ist der Unterschied zwischen SSG, SSR und ISR? Wie wähle ich?
• Statisches HTML beim Build – am schnellsten (~200 ms)
• Für selten ändernde Seiten (Blog-Liste, Über-Seite)
SSR (Server-Side Rendering):
• HTML bei jedem Request auf dem Server
• Für Echtzeit-Daten (Artikel-Detail, Nutzerprofil)
• Ladezeit ca. 800 ms
ISR (Incremental Static Regeneration):
• SSG + zeitgesteuerte Neugenerierung – Geschwindigkeit von SSG, Flexibilität von SSR
• Für gelegentlich aktualisierte Seiten (Blog-Liste stündlich)
Reines CSR (Client-Side Rendering):
• Rendering im Browser – schlechtes SEO, ~1500 ms
• Für Blogs nicht empfohlen
Strategie:
• Blog-Liste: SSG+ISR (export const revalidate = 3600)
• Artikel-Detail: dynamisches SSR
• Über-Seite: reines SSG
Vergleich: SSG ~200 ms, SSR ~800 ms, CSR ~1500 ms – der Unterschied ist klar.
Warum PostgreSQL statt MongoDB? Wie gut unterstützt Prisma PostgreSQL?
1) Bessere Prisma-Unterstützung – Typsicherheit, Migrations, Query-Optimierung
2) Relationale DB passt besser zu Artikeln, Nutzern, Kommentaren
3) Starke Volltextsuche für Blog-Suche
4) Stabil in Produktion, solide Transaktionen
Prisma-Vorteile:
• TypeScript-Typen direkt aus dem Schema – vollständige IDE-Unterstützung
• Mit Mongoose für MongoDB musste ich Typen manuell pflegen – fehleranfällig
• Prisma gibt mir beim Coden ein sicheres Gefühl
Für komplexe Beziehungsdaten: PostgreSQL + Prisma ist eine starke Kombination.
Was ist bei Vercel-Deploy von Next.js zu beachten? Wie optimiere ich die Produktion?
1) Code nach GitHub pushen
2) Repository in Vercel importieren – Next.js wird automatisch erkannt
3) Umgebungsvariablen (DATABASE_URL, GITHUB_ID, GITHUB_SECRET, NEXTAUTH_URL, NEXTAUTH_SECRET)
4) Deploy – ca. 2 Minuten bis live
Produktions-Optimierung:
1) Prisma Connection Pool:
• In prisma/schema.prisma datasource: directUrl = env("DIRECT_URL")
• Serverless: jeder Aufruf kann neue Verbindung öffnen – directUrl nutzt den Pool
2) Bilder:
• Next.js Image-Komponente – WebP, Lazy Loading, spürbar bessere Performance
3) DB: Supabase Free PostgreSQL (500 MB/Monat – für persönliche Blogs ausreichend)
Ergebnis:
• Lighthouse Desktop Performance 96, Mobile 92, SEO 100
• Zwei Tage von null auf live – produktionsreife Performance
8 Min. Lesezeit · Veröffentlicht am: 24. Nov. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Der vollständige Einstiegsleitfaden zum Next.js App Router. Server Components, Client Components, Spezialdateien und praktische Beispiele – damit Sie das neue Routing-System schnell verstehen und ohne Umwege loslegen.
Teil 1 von 51
Nächster
Next.js Pages Router zu App Router: Praxisleitfaden mit Migrationsstrategie und Fallen-Checkliste
Pages Router zu App Router migrieren? Dieser Artikel liefert eine schrittweise Migrationsstrategie, Lösungen für 7 häufige Fallen und Erfahrungen aus echten Produktionsprojekten – für ein sicheres Upgrade auf Next.js 14.
Teil 3 von 51
Ähnliche Beiträge
Next.js Advanced Routing: Route Groups, Nested Layouts, Parallel Routes und Intercepting Routes

Next.js Advanced Routing: Route Groups, Nested Layouts, Parallel Routes und Intercepting Routes
Next.js App Router in der Praxis: Route Groups und Nested Layouts gegen Verzeichnis-Chaos in großen Projekten


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen