Deutsch

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:

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:

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:

Verwendung von Validierungsbibliotheken

Für komplexere Validierungsszenarien sollten Sie die Verwendung von Validierungsbibliotheken in Betracht ziehen, wie zum Beispiel:

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:

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:

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:

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:

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:

Best Practices für Server Actions

Um die Vorteile von Server Actions zu maximieren, beachten Sie die folgenden Best Practices:

Häufige Fallstricke und wie man sie vermeidet

Obwohl Server Actions zahlreiche Vorteile bieten, gibt es einige häufige Fallstricke, die man beachten sollte:

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.