Kompleksowy przewodnik po Next.js 14 Server Actions, obejmujący najlepsze praktyki obsługi formularzy, walidację danych, aspekty bezpieczeństwa i zaawansowane techniki budowania nowoczesnych aplikacji internetowych.
Next.js 14 Server Actions: Opanowanie najlepszych praktyk obsługi formularzy
Next.js 14 wprowadza potężne funkcje do budowania wydajnych i przyjaznych dla użytkownika aplikacji internetowych. Wśród nich Server Actions wyróżniają się jako transformacyjny sposób obsługi przesyłania formularzy i mutacji danych bezpośrednio na serwerze. Ten przewodnik zawiera kompleksowy przegląd Server Actions w Next.js 14, koncentrując się na najlepszych praktykach obsługi formularzy, walidacji danych, bezpieczeństwie i zaawansowanych technikach. Przeanalizujemy praktyczne przykłady i dostarczymy przydatnych informacji, które pomogą Ci budować solidne i skalowalne aplikacje internetowe.
Czym są Next.js Server Actions?
Server Actions to asynchroniczne funkcje, które działają na serwerze i mogą być wywoływane bezpośrednio z komponentów React. Eliminują one potrzebę tradycyjnych tras API do obsługi przesyłania formularzy i mutacji danych, co skutkuje uproszczeniem kodu, poprawą bezpieczeństwa i zwiększeniem wydajności. Server Actions to komponenty React Server Components (RSCs), co oznacza, że są wykonywane na serwerze, co prowadzi do szybszego ładowania początkowego strony i lepszego SEO.
Kluczowe korzyści Server Actions:
- Uproszczony kod: Zredukuj ilość kodu szablonowego, eliminując potrzebę oddzielnych tras API.
- Poprawione bezpieczeństwo: Wykonywanie po stronie serwera minimalizuje luki w zabezpieczeniach po stronie klienta.
- Zwiększona wydajność: Wykonuj mutacje danych bezpośrednio na serwerze, aby uzyskać szybszy czas reakcji.
- Zoptymalizowane SEO: Wykorzystaj renderowanie po stronie serwera, aby uzyskać lepsze indeksowanie przez wyszukiwarki.
- Bezpieczeństwo typów: Korzystaj z kompleksowego bezpieczeństwa typów dzięki TypeScript.
Konfiguracja projektu Next.js 14
Przed zagłębieniem się w Server Actions upewnij się, że masz skonfigurowany projekt Next.js 14. Jeśli zaczynasz od zera, utwórz nowy projekt za pomocą następującego polecenia:
npx create-next-app@latest my-next-app
Upewnij się, że Twój projekt korzysta ze struktury katalogów app
, aby w pełni wykorzystać komponenty i akcje serwera.
Podstawowa obsługa formularzy za pomocą Server Actions
Zacznijmy od prostego przykładu: formularza, który przesyła dane w celu utworzenia nowego elementu w bazie danych. Użyjemy prostego formularza z polem wprowadzania i przyciskiem przesyłania.
Przykład: Tworzenie nowego elementu
Najpierw zdefiniuj funkcję Server Action w komponencie React. Ta funkcja będzie obsługiwać logikę przesyłania formularza na serwerze.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Symulacja interakcji z bazą danych
console.log('Tworzenie elementu:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Symulacja opóźnienia
console.log('Element utworzony pomyślnie!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
await createItem(formData);
setIsSubmitting(false);
}
return (
);
}
Wyjaśnienie:
- Dyrektywa
'use client'
wskazuje, że jest to komponent po stronie klienta. - Funkcja
createItem
jest oznaczona dyrektywą'use server'
, co wskazuje, że jest to Server Action. - Funkcja
handleSubmit
jest funkcją po stronie klienta, która wywołuje akcję serwera. Obsługuje również stan interfejsu użytkownika, taki jak wyłączenie przycisku podczas przesyłania. - Właściwość
action
elementu<form>
jest ustawiona na funkcjęhandleSubmit
. - Metoda
formData.get('name')
pobiera wartość pola wprowadzania 'name'. await new Promise
symuluje operację bazy danych i dodaje opóźnienie.
Walidacja danych
Walidacja danych ma kluczowe znaczenie dla zapewnienia integralności danych i zapobiegania lukom w zabezpieczeniach. Server Actions stanowią doskonałą okazję do przeprowadzenia walidacji po stronie serwera. Takie podejście pomaga ograniczyć ryzyko związane z samą walidacją po stronie klienta.
Przykład: Walidacja wprowadzanych danych
Zmodyfikuj Server Action createItem
, aby uwzględnić logikę walidacji.
// 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('Nazwa elementu musi mieć co najmniej 3 znaki.');
}
// Symulacja interakcji z bazą danych
console.log('Tworzenie elementu:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Symulacja opóźnienia
console.log('Element utworzony pomyślnie!');
}
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 || 'Wystąpił błąd.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Wyjaśnienie:
- Funkcja
createItem
sprawdza teraz, czyname
jest prawidłowa (ma co najmniej 3 znaki). - Jeśli walidacja nie powiedzie się, zgłaszany jest błąd.
- Funkcja
handleSubmit
jest aktualizowana, aby przechwytywać wszelkie błędy zgłaszane przez Server Action i wyświetlać komunikat o błędzie użytkownikowi.
Korzystanie z bibliotek walidacyjnych
W przypadku bardziej złożonych scenariuszy walidacji rozważ użycie bibliotek walidacyjnych, takich jak:
- Zod: Biblioteka deklaracji i walidacji schematów typu TypeScript-first.
- Yup: Konstruktor schematów JavaScript do analizowania, walidacji i przekształcania wartości.
Oto przykład użycia Zod:
// app/utils/validation.ts
import { z } from 'zod';
export const CreateItemSchema = z.object({
name: z.string().min(3, 'Nazwa elementu musi mieć co najmniej 3 znaki.'),
});
// 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 };
}
// Symulacja interakcji z bazą danych
console.log('Tworzenie elementu:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Symulacja opóźnienia
console.log('Element utworzony pomyślnie!');
}
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 || 'Wystąpił błąd.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Wyjaśnienie:
CreateItemSchema
definiuje reguły walidacji dla polaname
za pomocą Zod.- Metoda
safeParse
próbuje zweryfikować dane wejściowe. Jeśli walidacja nie powiedzie się, zwraca obiekt z błędami. - Obiekt
errors
zawiera szczegółowe informacje o błędach walidacji.
Aspekty bezpieczeństwa
Server Actions zwiększają bezpieczeństwo, wykonując kod na serwerze, ale nadal kluczowe jest przestrzeganie najlepszych praktyk w zakresie bezpieczeństwa, aby chronić aplikację przed typowymi zagrożeniami.
Zapobieganie atakom Cross-Site Request Forgery (CSRF)
Ataki CSRF wykorzystują zaufanie, jakie witryna ma w przeglądarce użytkownika. Aby zapobiec atakom CSRF, zaimplementuj mechanizmy ochrony przed CSRF.
Next.js automatycznie obsługuje ochronę przed CSRF podczas korzystania z Server Actions. Framework generuje i weryfikuje token CSRF dla każdego przesłania formularza, zapewniając, że żądanie pochodzi z Twojej aplikacji.
Obsługa uwierzytelniania i autoryzacji użytkowników
Upewnij się, że tylko autoryzowani użytkownicy mogą wykonywać określone działania. Zaimplementuj mechanizmy uwierzytelniania i autoryzacji, aby chronić wrażliwe dane i funkcje.
Oto przykład użycia NextAuth.js do ochrony Server Action:
// 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('Brak autoryzacji');
}
const name = formData.get('name') as string;
// Symulacja interakcji z bazą danych
console.log('Tworzenie elementu:', name, 'przez użytkownika:', session.user?.email);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Symulacja opóźnienia
console.log('Element utworzony pomyślnie!');
}
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 || 'Wystąpił błąd.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Wyjaśnienie:
- Funkcja
getServerSession
pobiera informacje o sesji użytkownika. - Jeśli użytkownik nie jest uwierzytelniony (brak sesji), zgłaszany jest błąd, co uniemożliwia wykonanie Server Action.
Oczyszczanie wprowadzanych danych
Oczyść wprowadzane dane, aby zapobiec atakom Cross-Site Scripting (XSS). Ataki XSS mają miejsce, gdy złośliwy kod jest wstrzykiwany do witryny, potencjalnie naruszając dane użytkownika lub funkcjonalność aplikacji.
Używaj bibliotek takich jak DOMPurify
lub sanitize-html
, aby oczyścić dane wprowadzone przez użytkownika przed przetworzeniem ich w Server Actions.
Zaawansowane techniki
Teraz, gdy omówiliśmy podstawy, przyjrzyjmy się kilku zaawansowanym technikom efektywnego korzystania z Server Actions.
Optymistyczne aktualizacje
Optymistyczne aktualizacje zapewniają lepsze wrażenia użytkownika, natychmiast aktualizując interfejs użytkownika tak, jakby akcja miała się powieść, jeszcze zanim serwer to potwierdzi. Jeśli akcja nie powiedzie się na serwerze, interfejs użytkownika zostaje przywrócony do poprzedniego stanu.
// 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;
// Symulacja interakcji z bazą danych
console.log('Aktualizowanie elementu:', id, 'z nazwą:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Symulacja opóźnienia
// Symulacja błędu (dla celów demonstracyjnych)
const shouldFail = Math.random() < 0.5;
if (shouldFail) {
throw new Error('Nie udało się zaktualizować elementu.');
}
console.log('Element zaktualizowany pomyślnie!');
return { name }; // Zwróć zaktualizowaną nazwę
}
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);
// Optymistyczna aktualizacja interfejsu użytkownika
const newName = formData.get('name') as string;
setItemName(newName);
try {
const result = await updateItem(id, formData);
//Jeśli sukces, aktualizacja jest już odzwierciedlona w interfejsie użytkownika przez setItemName
} catch (error: any) {
setErrorMessage(error.message || 'Wystąpił błąd.');
// Przywróć interfejs użytkownika w przypadku błędu
setItemName(initialName);
} finally {
setIsSubmitting(false);
}
}
return (
Aktualna nazwa: {itemName}
{errorMessage && {errorMessage}
}
);
}
Wyjaśnienie:
- Przed wywołaniem Server Action interfejs użytkownika jest natychmiast aktualizowany o nową nazwę elementu za pomocą
setItemName
. - Jeśli Server Action nie powiedzie się, interfejs użytkownika zostaje przywrócony do pierwotnej nazwy elementu.
Ponowna walidacja danych
Po tym, jak Server Action zmodyfikuje dane, może być konieczne ponowne zweryfikowanie danych w pamięci podręcznej, aby upewnić się, że interfejs użytkownika odzwierciedla najnowsze zmiany. Next.js udostępnia kilka sposobów ponownej walidacji danych:
- Ponowna walidacja ścieżki: Ponowna walidacja pamięci podręcznej dla określonej ścieżki.
- Ponowna walidacja tagu: Ponowna walidacja pamięci podręcznej dla danych powiązanych z określonym tagiem.
Oto przykład ponownej walidacji ścieżki po utworzeniu nowego elementu:
// 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;
// Symulacja interakcji z bazą danych
console.log('Tworzenie elementu:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Symulacja opóźnienia
console.log('Element utworzony pomyślnie!');
revalidatePath('/items'); // Ponowna walidacja ścieżki /items
}
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 || 'Wystąpił błąd.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Wyjaśnienie:
- Funkcja
revalidatePath('/items')
unieważnia pamięć podręczną dla ścieżki/items
, zapewniając, że następne żądanie do tej ścieżki pobierze najnowsze dane.
Najlepsze praktyki dla Server Actions
Aby zmaksymalizować korzyści płynące z Server Actions, rozważ następujące najlepsze praktyki:
- Dbaj o to, aby Server Actions były małe i skoncentrowane: Server Actions powinny wykonywać pojedyncze, dobrze zdefiniowane zadanie. Unikaj złożonej logiki w Server Actions, aby zachować czytelność i łatwość testowania.
- Używaj opisowych nazw: Nadawaj Server Actions opisowe nazwy, które jasno wskazują ich przeznaczenie.
- Elegancko obsługuj błędy: Zaimplementuj niezawodne zarządzanie błędami, aby zapewnić użytkownikowi pouczające informacje zwrotne i zapobiec awariom aplikacji.
- Dokładnie waliduj dane: Przeprowadź kompleksową walidację danych, aby zapewnić integralność danych i zapobiec lukom w zabezpieczeniach.
- Zabezpiecz swoje Server Actions: Zaimplementuj mechanizmy uwierzytelniania i autoryzacji, aby chronić wrażliwe dane i funkcje.
- Optymalizuj wydajność: Monitoruj wydajność Server Actions i optymalizuj je w razie potrzeby, aby zapewnić szybki czas reakcji.
- Efektywnie wykorzystuj buforowanie: Wykorzystaj mechanizmy buforowania Next.js, aby poprawić wydajność i zmniejszyć obciążenie bazy danych.
Typowe pułapki i jak ich unikać
Chociaż Server Actions oferują liczne zalety, należy pamiętać o kilku typowych pułapkach:
- Zbyt złożone Server Actions: Unikaj umieszczania zbyt dużej ilości logiki w pojedynczej Server Action. Dziel złożone zadania na mniejsze, łatwiejsze w zarządzaniu funkcje.
- Pomijanie obsługi błędów: Zawsze uwzględniaj obsługę błędów, aby wychwytywać nieoczekiwane błędy i zapewniać użytkownikowi pomocne informacje zwrotne.
- Ignorowanie najlepszych praktyk w zakresie bezpieczeństwa: Przestrzegaj najlepszych praktyk w zakresie bezpieczeństwa, aby chronić aplikację przed typowymi zagrożeniami, takimi jak XSS i CSRF.
- Zapominanie o ponownej walidacji danych: Upewnij się, że ponownie walidujesz dane w pamięci podręcznej po tym, jak Server Action zmodyfikuje dane, aby na bieżąco aktualizować interfejs użytkownika.
Wnioski
Next.js 14 Server Actions to potężny i wydajny sposób obsługi przesyłania formularzy i mutacji danych bezpośrednio na serwerze. Postępując zgodnie z najlepszymi praktykami opisanymi w tym przewodniku, możesz budować solidne, bezpieczne i wydajne aplikacje internetowe. Wykorzystaj Server Actions, aby uprościć kod, zwiększyć bezpieczeństwo i poprawić ogólne wrażenia użytkownika. Integrując te zasady, rozważ globalny wpływ wyborów programistycznych. Upewnij się, że Twoje formularze i procesy obsługi danych są dostępne, bezpieczne i przyjazne dla użytkownika dla zróżnicowanych odbiorców międzynarodowych. To zaangażowanie w inkluzywność nie tylko poprawi użyteczność Twojej aplikacji, ale także poszerzy jej zasięg i skuteczność w skali globalnej.