TL;DR
Server Actions in Next.js 15 sind der Standardweg für Formular-Mutationen — ohne separaten REST-Endpoint, mit eingebautem Schutz gegen einfache CSRF-Angriffe und natürlicher Integration mit React 19. Route Handlers (/api/...) gewinnen weiterhin, wenn Sie Webhooks externer Systeme, Rate Limiting pro IP, serverseitige reCAPTCHA-Prüfung, CRM-Integrationen über REST brauchen — oder wenn das Formular in einer großen Client-Komponente mit Analyse nach Erfolg (gtag) sitzt. Auf der produktiven DevStudio.it-Seite sendet das Formular in page.tsx per POST an /api/submissions — eine bewusste Entscheidung, kein „Legacy-Muster“. Unten: Vergleich, Zod-Validierung, revalidatePath, Sicherheit und ein schrittweiser Migrationsplan.
Für wen das ist
- Entwickler, die Lead-Formulare auf Next.js 15 (App Router) bauen
- Teams, die Migration von API Route zu Server Action (oder umgekehrt) erwägen
- Product Owner, die verstehen wollen, warum „einfacherer Code“ nicht immer die bessere Wahl ist
- Unternehmen mit DSGVO, reCAPTCHA, Mehrsprachigkeit (
/pl,/en,/de) und Conversion-Tracking nach Submit
Suchbegriffe (SEO)
nextjs server actions kontaktformular, server actions vs api route nextjs, revalidatePath formular, nextjs 15 formular validierung, csrf server actions nextjs
Zwei Wege für Formulare im App Router
Next.js 15 bietet zwei Hauptpfade für serverseitige Mutationen:
| Aspekt | Server Action | Route Handler (/api/...) |
|---|---|---|
| Aufruf | action={submitContact} oder formAction |
fetch('/api/submissions', { method: 'POST' }) |
| Datenformat | FormData (nativ) oder serialisiertes Objekt |
JSON, multipart, beliebig |
| CSRF | Eingebaute Next.js-Tokens | Sie müssen selbst absichern (oder SameSite-Cookies) |
| Externe Webhooks | Nein — nur Aufruf von Ihrer Seite | Ja — Standard-REST-Endpoint |
| Cache / Revalidierung | revalidatePath, revalidateTag |
Manuell nach Mutation oder separate Action |
| Testen | Schwieriger außerhalb RSC-Kontext | Postman, curl, CI-Integrationen |
Eine Server Action ist eine mit 'use server' markierte Funktion, die React direkt aus dem Formular aufruft. Next.js serialisiert das Ergebnis, behandelt POST mit verstecktem Action-Feld und — im Gegensatz zu klassischen APIs — stellt keine öffentliche URL bereit, die jeder mit curl spammen könnte (technisch existiert POST auf die aktuelle Seite).
Ein Route Handler ist eine route.ts-Datei unter app/api/, bekannt aus Pages Router und REST-Ökosystem. Er bleibt Standard, wenn Submit-Logik über „in DB speichern und Mail senden“ hinausgeht.
Produktionsmuster: Formular in page.tsx + /api/submissions
Im DevStudio.it-Projekt (Next.js 15.5.18) lebt das Kontaktformular in einer großen Client-Komponente src/app/[locale]/page.tsx. Nach clientseitiger E-Mail-Validierung und reCAPTCHA v3 folgt:
const response = await fetch('/api/submissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name, email, phone, projectType, budget, description,
locale,
recaptchaToken,
}),
});Nach response.ok feuert die Seite GA4 (generate_lead) und Google Ads (conversion_event_submit_lead_form), dann Redirect zur Danke-Seite mit event_timeout: 2000 — damit Tags vor Navigation fertig werden.
Der Route Handler in src/app/api/submissions/route.ts macht deutlich mehr als ein einfacher Insert:
- Rate Limiting pro IP (
checkEmailRateLimit) - reCAPTCHA-Verifikation mit Google (
siteverify, Score-Schwelle 0.5) - Prisma-Persistenz (
submission.create) - Transaktions-Mails (Admin + Kunde, Templates pro Locale)
- IP- und User-Agent-Logging
Das ist kein „wir sind aus Versehen beim alten API geblieben“. Es ist Architektur, in der Submit eine Mini-Integrations-Pipeline ist und Client-Erfolg mit Marketing-Analyse synchron bleiben muss — was mit Server Actions entweder den ganzen Flow nach RSC verschiebt oder eine Hybrid-Lösung erfordert.
Server Actions — so sieht es in Next.js 15 aus
Minimales Server-Action-Beispiel für ein Kontaktformular:
// app/actions/contact.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
const contactSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
description: z.string().min(10).max(5000),
locale: z.enum(['pl', 'en', 'de']),
});
export type ContactFormState = {
ok: boolean;
message?: string;
fieldErrors?: Record<string, string[]>;
};
export async function submitContact(
_prev: ContactFormState,
formData: FormData
): Promise<ContactFormState> {
const parsed = contactSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
description: formData.get('description'),
locale: formData.get('locale'),
});
if (!parsed.success) {
return {
ok: false,
fieldErrors: parsed.error.flatten().fieldErrors,
};
}
// ... DB, Mail, reCAPTCHA ...
revalidatePath(`/${parsed.data.locale}`);
return { ok: true, message: 'Erfolgreich gesendet' };
}Formular mit React 19 useActionState (früher useFormState):
'use client';
import { useActionState } from 'react';
import { submitContact } from '@/app/actions/contact';
export function ContactForm() {
const [state, formAction, pending] = useActionState(submitContact, { ok: false });
return (
<form action={formAction}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="description" required />
<input type="hidden" name="locale" value="de" />
<button type="submit" disabled={pending}>
{pending ? 'Wird gesendet…' : 'Senden'}
</button>
{state.fieldErrors?.email && <p>{state.fieldErrors.email[0]}</p>}
{state.ok && <p>{state.message}</p>}
</form>
);
}Vorteile:
- Progressive Enhancement — Formular funktioniert ohne JS (Full-Page-Submit)
- Kein manuelles
fetchund JSON-Parsing im Client - Validierung und Mutation an einem Server-Ort
pendingaus dem Hook — eingebauter Ladezustand
Validierung: Client vs Server
Die Regel 2026 bleibt: Client-Validierung ist UX; Server-Validierung ist Sicherheit.
Im aktuellen Projekt prüft der Client das E-Mail-Format vor dem Senden (validateEmail), der Route Handler verifiziert Pflichtfelder und lehnt Requests ohne reCAPTCHA-Token ab. Diese Aufteilung ist korrekt.
Mit Server Actions ist Zod (oder Valibot) am Einstieg Standard:
const parsed = contactSchema.safeParse(Object.fromEntries(formData));Weitere Muster:
- Normalisierung —
email.trim().toLowerCase()vor Speicherung - Honeypot — verstecktes Feld
website; wenn ausgefüllt, stiller Erfolg ohne Speicherung - Längenlimits — Schutz vor 10-MB-Payloads in
description - Einheitliche Fehlermeldungen pro Locale — Zod-Codes auf Übersetzungen aus
messages/de.jsonmappen
Vertrauen Sie niemals allein auf HTML required — Bots und curl umgehen das.
revalidatePath und UI-Aktualisierung
Nach erfolgreichem Speichern rufen Server Actions oft auf:
import { revalidatePath } from 'next/cache';
revalidatePath('/de/admin'); // konkreter Pfad
revalidatePath('/de', 'layout'); // gesamtes Layout-Segment
revalidateTag('submissions'); // wenn Liste fetch mit Tag nutztrevalidatePath invalidiert RSC-Cache für diese Route — Admin sieht den neuen Lead ohne Hard Refresh. Im Route Handler müssen Sie entweder:
- dieselbe Funktion aus einem Shared-Modul aufrufen (
import { revalidatePath } from 'next/cache'funktioniert inroute.ts), - oder auf SWR / React Query im Client mit
mutate()nach erfolgreichem fetch setzen.
Für ein öffentliches Kontaktformular ist revalidatePath selten nötig — Nutzer sehen keine Einreichungsliste. Entscheidend wird es im Admin-Panel oder bei Blog-Kommentaren.
Sicherheit: CSRF und Server Actions
Next.js Server Actions senden POST mit Action-Header und Token, die das Framework prüft. Das begrenzt klassisches CSRF von fremden Domains — ein Angreifer kann ohne Build-Secret / Origin kein gültiges Token erzeugen.
Route Handlers haben diesen Schutz standardmäßig nicht. Öffentliches POST /api/submissions kann gespammt werden:
- Rate Limiting (wie im Projekt) — erste Verteidigungslinie
- reCAPTCHA v3 — zweite Linie
Origin/Referer-Prüfung — zusätzliche Schicht- API-Key im Header — wenn der Endpoint nur Ihrer App dient (kein öffentliches Formular)
In Server Actions ist reCAPTCHA weiter nötig — Token im Client generieren (grecaptcha.execute), in FormData übergeben, in der Action mit demselben Code wie im Route Handler verifizieren.
Hinweis: Server Actions ersetzen keinen Schutz vor Botnetzen — sie vereinfachen nur das CSRF-Modell für First-Party-Formulare.
Wann beim Route Handler (API route) bleiben
Bleiben Sie bei /api/submissions (oder ähnlich), wenn:
1. Externe Integrationen und Webhooks
Stripe, Calendly, Typeform senden POST an eine feste URL. Route Handlers sind der natürliche Ort. Server Actions haben keine öffentliche Adresse für das Stripe-Dashboard.
2. Client ist bereits client-heavy
Das Formular in page.tsx hat hunderte Zeilen UI (Framer Motion, Sticky Header, Portfolio). Submit auf Server Actions zu verlagern erfordert entweder Auslagern in eine Komponente mit useActionState oder Beibehalten von fetch — beides ist in Ordnung.
3. Analyse nach Erfolg im Browser
gtag('event', 'generate_lead') muss im Client nach HTTP 200 laufen. Server Action kann { ok: true } zurückgeben, Client ruft weiter gtag auf — Hybrid funktioniert. Aber bei redirect() in der Action zur Danke-Seite verlieren Sie den Moment für Ads-Conversion-Callbacks. Daher aktuelles Muster: fetch → gtag mit event_callback → redirect.
4. Testen und Monitoring
REST-Endpoints lassen sich leicht in Postman, Load-Tests (k6) und Reverse-Proxy-Logs testen (POST /api/submissions 429). Server Actions loggen als POST auf eine Seite — weniger lesbar in nginx access logs.
5. Mehrere Konsumenten derselben API
Mobile App, Chatbot (/api/chatbot/submit), WordPress-Widget — ein Route Handler, viele Clients. Im Projekt hat der Chatbot einen separaten Endpoint, die Logik ist analog.
Wann Server Actions wählen
- Neues, einfaches Formular in RSC oder kleiner Client-Komponente
- Kein gtag-vor-Redirect — Danke-Seite reicht
- Progressive Enhancement ohne separates API schreiben
- Admin-Panel — Inhaltsbearbeitung, Submission-Status, Kommentare mit
revalidatePath - Kleinere Angriffsfläche — eine Action-Datei statt
route.ts+ Response-Typen
Migrationsplan: API Route zu Server Action (optional)
Bei Migration eines DevStudio-ähnlichen Formulars:
- Logik extrahieren aus
route.tsnachlib/submissions/create-submission.ts(pure async function). app/actions/submit-contact.tsmit'use server'— ruft Shared Lib +revalidatePath('/de/admin').POST /api/submissionsbehalten als dünner Wrapper mit derselben Lib (Backward Compatibility, Tests).- Formular aus
page.tsxnachContactFormSection.tsxmituseActionStateauslagern. - gtag-Block nach
state.okinuseEffectbehalten — Analyse bleibt im Client. - reCAPTCHA, Rate Limit und Mails auf Staging testen.
Migration muss kein Big Bang sein. Shared Lib + zwei Entrypoints ist ein reifer Zwischenschritt.
Progressive Enhancement vs SPA-Submit
Server Actions mit nativem <form action={...}> funktionieren ohne JavaScript. Das aktuelle Formular mit onSubmit, reCAPTCHA und fetch braucht JS — Nutzer ohne Skripte können keinen Lead senden. Für eine B2B-Software-House-Seite akzeptabel (99 %+ Traffic hat JS), aber bewusst in der Spec dokumentieren.
Wenn DSGVO Betrieb ohne Tracking-Cookies verlangt, liefert Server Action + Redirect auf /danke den Lead ins CRM ohne gtag — konsistent mit „Nur notwendige“ im Cookie-Banner.
FAQ
Ersetzen Server Actions Route Handlers in Next.js 15?
Nicht vollständig. Actions sind optimal für Mutationen aus Ihrer React-App. Route Handlers bleiben Standard für Webhooks, öffentliches REST, Multipart-Uploads und Third-Party-Integrationen. Ein Projekt hat oft beides.
Ist eine Server Action sicherer als eine API route?
Bezüglich CSRF — ja, standardmäßig. Bezüglich Spam und Bots — nein; Sie brauchen weiter Rate Limits, CAPTCHA und Server-Validierung. Ein öffentlicher Endpoint bleibt öffentlich — Actions können auch massenhaft aufgerufen werden, wenn jemand das Token reverse-engineert (schwerer als curl auf /api/..., aber nicht unmöglich).
Wie übergebe ich ein reCAPTCHA-Token in einer Server Action?
Token in 'use client' vor Submit generieren (grecaptcha.execute), als <input type="hidden" name="recaptchaToken" value={token} /> oder in FormData bei programmatischem Submit. In der Server Action denselben fetch zu google.com/recaptcha/api/siteverify wie im Route Handler.
Kann ich redirect() in einer Server Action nach Erfolg nutzen?
Ja — import { redirect } from 'next/navigation'. Beachten: Redirect in der Action unterbricht Render und liefert kein JSON an den Client. Wenn Sie zuerst gtag brauchen, Redirect im Client (aktuelles Muster) oder Conversion-Tags im Layout der Danke-Seite.
Ist revalidatePath nach Kontaktformular nötig?
Meist nicht auf der öffentlichen Seite — Nutzer sehen keine gecachte Liste. Ja im Admin-Panel, Blog mit Kommentaren oder wenn Sie „Ihre letzten Einreichungen“ nach Submit rendern.
JSON oder FormData in Server Actions?
FormData — natives <form>-Format. Für komplexe Felder JSON in ein Hidden-Feld serialisieren. Vermeiden Sie, gesamten React-State zu senden — nur Formulardaten.
Zusammenfassung
Next.js 15.5.18 erzwingt Server Actions nicht auf Formularen — es bietet eine bessere Alternative für einfache Mutationen mit Progressive Enhancement und eingebautem CSRF. Das produktive Formular auf DevStudio.it mit fetch('/api/submissions'), reCAPTCHA, Rate Limiting, Mails und gtag nach Erfolg ist ein gerechtfertigter Route Handler, keine technische Schuld. Wählen Sie Server Actions, wenn Submit einfach ist und nahe an RSC lebt; bleiben Sie bei API, wenn Submit Integrations-Pipeline, Webhooks oder enge Sync mit Google Ads ist. In beiden Fällen: Zod auf dem Server, nie nur HTML required, und Shared Lib bei geplanter Migration.
Formular bei Ihnen umsetzen?
- Kontakt — wir entwerfen ein Formular für Ihre Kampagne und Ihren Stack
- Websites — Next.js 15, SEO, Conversions und Sicherheit
- Referenzen — produktive Deployments mit Analyse