Ein umfassender Leitfaden zu Next.js 14 Server Actions, der Best Practices für die Formularverarbeitung, Datenvalidierung, Sicherheitsaspekte und fortgeschrittene Techniken für die Entwicklung moderner Webanwendungen behandelt.
Next.js 14 Server Actions: Best Practices für die Formularverarbeitung
Next.js 14 führt leistungsstarke Funktionen für die Entwicklung performanter und benutzerfreundlicher Webanwendungen ein. Unter diesen stechen Server Actions als transformative Methode hervor, um Formularübermittlungen und Datenmutationen direkt auf dem Server zu handhaben. Dieser Leitfaden bietet einen umfassenden Überblick über Server Actions in Next.js 14, mit Schwerpunkt auf Best Practices für die Formularverarbeitung, Datenvalidierung, Sicherheit und fortgeschrittene Techniken. Wir werden praktische Beispiele untersuchen und umsetzbare Einblicke geben, die Ihnen helfen, robuste und skalierbare Webanwendungen zu erstellen.
Was sind Next.js Server Actions?
Server Actions sind asynchrone Funktionen, die auf dem Server ausgeführt und direkt von React-Komponenten aufgerufen werden können. Sie eliminieren die Notwendigkeit traditioneller API-Routen für die Handhabung von Formularübermittlungen und Datenmutationen, was zu vereinfachtem Code, verbesserter Sicherheit und erhöhter Leistung führt. Server Actions sind React Server Components (RSCs), was bedeutet, dass sie auf dem Server ausgeführt werden, was zu schnelleren initialen Seitenladezeiten und verbessertem SEO führt.
Hauptvorteile von Server Actions:
- Vereinfachter Code: Reduzieren Sie Boilerplate-Code, indem Sie die Notwendigkeit separater API-Routen eliminieren.
- Verbesserte Sicherheit: Die serverseitige Ausführung minimiert clientseitige Schwachstellen.
- Gesteigerte Leistung: Führen Sie Datenmutationen direkt auf dem Server aus, um schnellere Antwortzeiten zu erzielen.
- Optimiertes SEO: Nutzen Sie serverseitiges Rendering für eine bessere Indexierung durch Suchmaschinen.
- Typsicherheit: Profitieren Sie von durchgängiger Typsicherheit mit TypeScript.
Einrichten Ihres Next.js 14-Projekts
Bevor Sie in Server Actions eintauchen, stellen Sie sicher, dass Sie ein Next.js 14-Projekt eingerichtet haben. Wenn Sie von Grund auf neu beginnen, erstellen Sie ein neues Projekt mit dem folgenden Befehl:
npx create-next-app@latest my-next-app
Stellen Sie sicher, dass Ihr Projekt die app
-Verzeichnisstruktur verwendet, um die Vorteile von Server Components und Actions voll auszuschöpfen.
Grundlegende Formularverarbeitung mit Server Actions
Beginnen wir mit einem einfachen Beispiel: einem Formular, das Daten übermittelt, um einen neuen Eintrag in einer Datenbank zu erstellen. Wir verwenden ein einfaches Formular mit einem Eingabefeld und einem Senden-Button.
Beispiel: Erstellen eines neuen Eintrags
Definieren Sie zuerst eine Server Action-Funktion innerhalb Ihrer React-Komponente. Diese Funktion wird die Logik zur Formularübermittlung auf dem Server handhaben.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Datenbankinteraktion simulieren
console.log('Erstelle Eintrag:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Latenz simulieren
console.log('Eintrag erfolgreich erstellt!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
await createItem(formData);
setIsSubmitting(false);
}
return (
);
}
Erklärung:
- Die
'use client'
-Direktive zeigt an, dass es sich um eine Client-Komponente handelt. - Die
createItem
-Funktion ist mit der'use server'
-Direktive markiert, was anzeigt, dass es sich um eine Server Action handelt. - Die
handleSubmit
-Funktion ist eine clientseitige Funktion, die die Server Action aufruft. Sie kümmert sich auch um den UI-Zustand, wie z. B. das Deaktivieren des Buttons während der Übermittlung. - Das
action
-Prop des<form>
-Elements ist auf diehandleSubmit
-Funktion gesetzt. - Die
formData.get('name')
-Methode ruft den Wert des 'name'-Eingabefeldes ab. - Das
await new Promise
simuliert eine Datenbankoperation und fügt Latenz hinzu.
Datenvalidierung
Datenvalidierung ist entscheidend, um die Datenintegrität zu gewährleisten und Sicherheitslücken zu vermeiden. Server Actions bieten eine ausgezeichnete Möglichkeit, serverseitige Validierung durchzuführen. Dieser Ansatz hilft, Risiken zu minimieren, die allein mit clientseitiger Validierung verbunden sind.
Beispiel: Validierung von Eingabedaten
Modifizieren Sie die createItem
Server Action, um Validierungslogik hinzuzufügen.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
if (!name || name.length < 3) {
throw new Error('Der Name des Eintrags muss mindestens 3 Zeichen lang sein.');
}
// Datenbankinteraktion simulieren
console.log('Erstelle Eintrag:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Latenz simulieren
console.log('Eintrag erfolgreich erstellt!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Ein Fehler ist aufgetreten.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Erklärung:
- Die
createItem
-Funktion prüft nun, ob dername
gültig ist (mindestens 3 Zeichen lang). - Wenn die Validierung fehlschlägt, wird ein Fehler geworfen.
- Die
handleSubmit
-Funktion wurde aktualisiert, um alle von der Server Action geworfenen Fehler abzufangen und dem Benutzer eine Fehlermeldung anzuzeigen.
Verwendung von Validierungsbibliotheken
Für komplexere Validierungsszenarien sollten Sie die Verwendung von Validierungsbibliotheken in Betracht ziehen, wie zum Beispiel:
- Zod: Eine TypeScript-first Schema-Deklarations- und Validierungsbibliothek.
- Yup: Ein JavaScript Schema-Builder zum Parsen, Validieren und Transformieren von Werten.
Hier ist ein Beispiel mit Zod:
// app/utils/validation.ts
import { z } from 'zod';
export const CreateItemSchema = z.object({
name: z.string().min(3, 'Der Name des Eintrags muss mindestens 3 Zeichen lang sein.'),
});
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
const validatedFields = CreateItemSchema.safeParse({ name });
if (!validatedFields.success) {
return { errors: validatedFields.error.flatten().fieldErrors };
}
// Datenbankinteraktion simulieren
console.log('Erstelle Eintrag:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Latenz simulieren
console.log('Eintrag erfolgreich erstellt!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Ein Fehler ist aufgetreten.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Erklärung:
- Das
CreateItemSchema
definiert die Validierungsregeln für dasname
-Feld mit Zod. - Die
safeParse
-Methode versucht, die Eingabedaten zu validieren. Wenn die Validierung fehlschlägt, gibt sie ein Objekt mit den Fehlern zurück. - Das
errors
-Objekt enthält detaillierte Informationen über die Validierungsfehler.
Sicherheitsaspekte
Server Actions verbessern die Sicherheit, indem sie Code auf dem Server ausführen, aber es ist dennoch entscheidend, Best Practices für die Sicherheit zu befolgen, um Ihre Anwendung vor gängigen Bedrohungen zu schützen.
Verhinderung von Cross-Site Request Forgery (CSRF)
CSRF-Angriffe nutzen das Vertrauen aus, das eine Website in den Browser eines Benutzers hat. Um CSRF-Angriffe zu verhindern, implementieren Sie CSRF-Schutzmechanismen.
Next.js handhabt den CSRF-Schutz automatisch bei der Verwendung von Server Actions. Das Framework generiert und validiert ein CSRF-Token für jede Formularübermittlung und stellt so sicher, dass die Anfrage von Ihrer Anwendung stammt.
Umgang mit Benutzerauthentifizierung und -autorisierung
Stellen Sie sicher, dass nur autorisierte Benutzer bestimmte Aktionen ausführen können. Implementieren Sie Authentifizierungs- und Autorisierungsmechanismen, um sensible Daten und Funktionalitäten zu schützen.
Hier ist ein Beispiel mit NextAuth.js, um eine Server Action zu schützen:
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';
async function createItem(formData: FormData) {
'use server'
const session = await getServerSession(authOptions);
if (!session) {
throw new Error('Unautorisiert');
}
const name = formData.get('name') as string;
// Datenbankinteraktion simulieren
console.log('Erstelle Eintrag:', name, 'von Benutzer:', session.user?.email);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Latenz simulieren
console.log('Eintrag erfolgreich erstellt!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Ein Fehler ist aufgetreten.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Erklärung:
- Die
getServerSession
-Funktion ruft die Sitzungsinformationen des Benutzers ab. - Wenn der Benutzer nicht authentifiziert ist (keine Sitzung), wird ein Fehler geworfen, was die Ausführung der Server Action verhindert.
Bereinigung von Eingabedaten
Bereinigen Sie Eingabedaten, um Cross-Site Scripting (XSS)-Angriffe zu verhindern. XSS-Angriffe treten auf, wenn bösartiger Code in eine Website eingeschleust wird, was potenziell Benutzerdaten oder Anwendungsfunktionalitäten kompromittieren kann.
Verwenden Sie Bibliotheken wie DOMPurify
oder sanitize-html
, um vom Benutzer bereitgestellte Eingaben zu bereinigen, bevor Sie sie in Ihren Server Actions verarbeiten.
Fortgeschrittene Techniken
Nachdem wir die Grundlagen behandelt haben, wollen wir uns einige fortgeschrittene Techniken für den effektiven Einsatz von Server Actions ansehen.
Optimistische Updates
Optimistische Updates bieten eine bessere Benutzererfahrung, indem sie die Benutzeroberfläche sofort aktualisieren, als ob die Aktion erfolgreich wäre, noch bevor der Server dies bestätigt. Wenn die Aktion auf dem Server fehlschlägt, wird die Benutzeroberfläche in ihren vorherigen Zustand zurückversetzt.
// app/components/UpdateItemForm.tsx
'use client';
import { useState } from 'react';
async function updateItem(id: string, formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Datenbankinteraktion simulieren
console.log('Aktualisiere Eintrag:', id, 'mit Name:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Latenz simulieren
// Fehlschlag simulieren (zu Demonstrationszwecken)
const shouldFail = Math.random() < 0.5;
if (shouldFail) {
throw new Error('Aktualisierung des Eintrags fehlgeschlagen.');
}
console.log('Eintrag erfolgreich aktualisiert!');
return { name }; // Den aktualisierten Namen zurückgeben
}
export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [itemName, setItemName] = useState(initialName);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
// Die UI optimistisch aktualisieren
const newName = formData.get('name') as string;
setItemName(newName);
try {
const result = await updateItem(id, formData);
// Bei Erfolg ist die Aktualisierung bereits durch setItemName in der UI sichtbar
} catch (error: any) {
setErrorMessage(error.message || 'Ein Fehler ist aufgetreten.');
// Die UI bei einem Fehler zurücksetzen
setItemName(initialName);
} finally {
setIsSubmitting(false);
}
}
return (
Aktueller Name: {itemName}
{errorMessage && {errorMessage}
}
);
}
Erklärung:
- Vor dem Aufruf der Server Action wird die UI sofort mit dem neuen Eintragsnamen mithilfe von
setItemName
aktualisiert. - Wenn die Server Action fehlschlägt, wird die UI auf den ursprünglichen Eintragsnamen zurückgesetzt.
Daten neu validieren
Nachdem eine Server Action Daten modifiziert hat, müssen Sie möglicherweise zwischengespeicherte Daten neu validieren, um sicherzustellen, dass die Benutzeroberfläche die neuesten Änderungen widerspiegelt. Next.js bietet mehrere Möglichkeiten, Daten neu zu validieren:
- Pfad neu validieren (Revalidate Path): Den Cache für einen bestimmten Pfad neu validieren.
- Tag neu validieren (Revalidate Tag): Den Cache für Daten, die mit einem bestimmten Tag verknüpft sind, neu validieren.
Hier ist ein Beispiel für die Neuvalidierung eines Pfades nach dem Erstellen eines neuen Eintrags:
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { revalidatePath } from 'next/cache';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Datenbankinteraktion simulieren
console.log('Erstelle Eintrag:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Latenz simulieren
console.log('Eintrag erfolgreich erstellt!');
revalidatePath('/items'); // Den /items-Pfad neu validieren
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Ein Fehler ist aufgetreten.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Erklärung:
- Die Funktion
revalidatePath('/items')
macht den Cache für den Pfad/items
ungültig und stellt sicher, dass die nächste Anfrage an diesen Pfad die neuesten Daten abruft.
Best Practices für Server Actions
Um die Vorteile von Server Actions zu maximieren, beachten Sie die folgenden Best Practices:
- Halten Sie Server Actions klein und fokussiert: Server Actions sollten eine einzelne, gut definierte Aufgabe ausführen. Vermeiden Sie komplexe Logik innerhalb von Server Actions, um die Lesbarkeit und Testbarkeit zu erhalten.
- Verwenden Sie beschreibende Namen: Geben Sie Ihren Server Actions beschreibende Namen, die ihren Zweck klar angeben.
- Fehler elegant behandeln: Implementieren Sie eine robuste Fehlerbehandlung, um dem Benutzer informatives Feedback zu geben und Anwendungsabstürze zu verhindern.
- Daten gründlich validieren: Führen Sie eine umfassende Datenvalidierung durch, um die Datenintegrität zu gewährleisten und Sicherheitslücken zu vermeiden.
- Sichern Sie Ihre Server Actions: Implementieren Sie Authentifizierungs- und Autorisierungsmechanismen, um sensible Daten und Funktionalitäten zu schützen.
- Leistung optimieren: Überwachen Sie die Leistung Ihrer Server Actions und optimieren Sie sie bei Bedarf, um schnelle Antwortzeiten zu gewährleisten.
- Caching effektiv nutzen: Nutzen Sie die Caching-Mechanismen von Next.js, um die Leistung zu verbessern und die Datenbanklast zu reduzieren.
Häufige Fallstricke und wie man sie vermeidet
Obwohl Server Actions zahlreiche Vorteile bieten, gibt es einige häufige Fallstricke, die man beachten sollte:
- Übermäßig komplexe Server Actions: Vermeiden Sie es, zu viel Logik in eine einzelne Server Action zu packen. Teilen Sie komplexe Aufgaben in kleinere, besser verwaltbare Funktionen auf.
- Vernachlässigung der Fehlerbehandlung: Fügen Sie immer eine Fehlerbehandlung hinzu, um unerwartete Fehler abzufangen und dem Benutzer hilfreiches Feedback zu geben.
- Ignorieren von Sicherheits-Best-Practices: Befolgen Sie Sicherheits-Best-Practices, um Ihre Anwendung vor gängigen Bedrohungen wie XSS und CSRF zu schützen.
- Vergessen, Daten neu zu validieren: Stellen Sie sicher, dass Sie zwischengespeicherte Daten nach einer Datenänderung durch eine Server Action neu validieren, um die Benutzeroberfläche auf dem neuesten Stand zu halten.
Fazit
Next.js 14 Server Actions bieten eine leistungsstarke und effiziente Möglichkeit, Formularübermittlungen und Datenmutationen direkt auf dem Server zu handhaben. Indem Sie die in diesem Leitfaden beschriebenen Best Practices befolgen, können Sie robuste, sichere und performante Webanwendungen erstellen. Nutzen Sie Server Actions, um Ihren Code zu vereinfachen, die Sicherheit zu erhöhen und die allgemeine Benutzererfahrung zu verbessern. Berücksichtigen Sie bei der Integration dieser Prinzipien die globalen Auswirkungen Ihrer Entwicklungsentscheidungen. Stellen Sie sicher, dass Ihre Formulare und Datenverarbeitungsprozesse für ein vielfältiges internationales Publikum zugänglich, sicher und benutzerfreundlich sind. Dieses Engagement für Inklusivität wird nicht nur die Benutzerfreundlichkeit Ihrer Anwendung verbessern, sondern auch ihre Reichweite und Effektivität auf globaler Ebene erweitern.