Dansk

En komplet guide til Next.js 14 Server Actions. Lær bedste praksisser for formularhåndtering, datavalidering, sikkerhed og avancerede teknikker.

Next.js 14 Server Actions: Bedste praksisser for formularhåndtering

Next.js 14 introducerer kraftfulde funktioner til at bygge performante og brugervenlige webapplikationer. Blandt disse skiller Server Actions sig ud som en transformerende måde at håndtere formularindsendelser og datamutationer direkte på serveren. Denne guide giver et omfattende overblik over Server Actions i Next.js 14, med fokus på bedste praksisser for formularhåndtering, datavalidering, sikkerhed og avancerede teknikker. Vi vil udforske praktiske eksempler og give handlingsorienterede indsigter for at hjælpe dig med at bygge robuste og skalerbare webapplikationer.

Hvad er Next.js Server Actions?

Server Actions er asynkrone funktioner, der kører på serveren og kan kaldes direkte fra React-komponenter. De eliminerer behovet for traditionelle API-ruter til håndtering af formularindsendelser og datamutationer, hvilket resulterer i forenklet kode, forbedret sikkerhed og øget ydeevne. Server Actions er React Server Components (RSC'er), hvilket betyder, at de udføres på serveren, hvilket fører til hurtigere indlæsning af sider og forbedret SEO.

Væsentlige fordele ved Server Actions:

Opsætning af dit Next.js 14-projekt

Før du dykker ned i Server Actions, skal du sikre dig, at du har et Next.js 14-projekt opsat. Hvis du starter fra bunden, kan du oprette et nyt projekt med følgende kommando:

npx create-next-app@latest my-next-app

Sørg for, at dit projekt bruger app-mappestrukturen for at drage fuld fordel af Server Components og Actions.

Grundlæggende formularhåndtering med Server Actions

Lad os starte med et simpelt eksempel: en formular, der sender data for at oprette et nyt element i en database. Vi vil bruge en simpel formular med et inputfelt og en submit-knap.

Eksempel: Oprettelse af et nyt element

Først skal du definere en Server Action-funktion i din React-komponent. Denne funktion vil håndtere logikken for formularindsendelse på serveren.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simuler interaktion med databasen
  console.log('Opretter element:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler latenstid

  console.log('Element oprettet succesfuldt!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    await createItem(formData);
    setIsSubmitting(false);
  }

  return (
    
); }

Forklaring:

Datavalidering

Datavalidering er afgørende for at sikre dataintegritet og forhindre sikkerhedssårbarheder. Server Actions giver en fremragende mulighed for at udføre server-side validering. Denne tilgang hjælper med at mindske risici forbundet med kun at have klient-side validering.

Eksempel: Validering af inputdata

Modificer createItem Server Action til at inkludere valideringslogik.

// 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('Navnet på elementet skal være mindst 3 tegn langt.');
  }

  // Simuler interaktion med databasen
  console.log('Opretter element:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler latenstid

  console.log('Element oprettet succesfuldt!');
}

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 || 'Der opstod en fejl.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Forklaring:

Brug af valideringsbiblioteker

For mere komplekse valideringsscenarier kan du overveje at bruge valideringsbiblioteker som:

Her er et eksempel, der bruger Zod:

// app/utils/validation.ts
import { z } from 'zod';

export const CreateItemSchema = z.object({
  name: z.string().min(3, 'Navnet på elementet skal være mindst 3 tegn langt.'),
});
// 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 };
  }

  // Simuler interaktion med databasen
  console.log('Opretter element:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler latenstid

  console.log('Element oprettet succesfuldt!');
}

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 || 'Der opstod en fejl.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Forklaring:

Sikkerhedsovervejelser

Server Actions forbedrer sikkerheden ved at eksekvere kode på serveren, men det er stadig afgørende at følge bedste praksisser for sikkerhed for at beskytte din applikation mod almindelige trusler.

Forebyggelse af Cross-Site Request Forgery (CSRF)

CSRF-angreb udnytter den tillid, et website har til en brugers browser. For at forhindre CSRF-angreb skal du implementere mekanismer til CSRF-beskyttelse.

Next.js håndterer automatisk CSRF-beskyttelse, når du bruger Server Actions. Frameworket genererer og validerer et CSRF-token for hver formularindsendelse, hvilket sikrer, at anmodningen stammer fra din applikation.

Håndtering af brugerautentificering og -autorisation

Sørg for, at kun autoriserede brugere kan udføre bestemte handlinger. Implementer autentificerings- og autorisationsmekanismer for at beskytte følsomme data og funktionalitet.

Her er et eksempel, der bruger NextAuth.js til at beskytte en 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('Uautoriseret');
  }

  const name = formData.get('name') as string;

  // Simuler interaktion med databasen
  console.log('Opretter element:', name, 'af bruger:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler latenstid

  console.log('Element oprettet succesfuldt!');
}

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 || 'Der opstod en fejl.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Forklaring:

Sanering af inputdata

Sanér inputdata for at forhindre Cross-Site Scripting (XSS)-angreb. XSS-angreb opstår, når ondsindet kode injiceres i et website, hvilket potentielt kan kompromittere brugerdata eller applikationsfunktionalitet.

Brug biblioteker som DOMPurify eller sanitize-html til at sanere brugerleveret input, før det behandles i dine Server Actions.

Avancerede teknikker

Nu hvor vi har dækket det grundlæggende, lad os udforske nogle avancerede teknikker til at bruge Server Actions effektivt.

Optimistiske opdateringer

Optimistiske opdateringer giver en bedre brugeroplevelse ved øjeblikkeligt at opdatere UI'en, som om handlingen vil lykkes, selv før serveren bekræfter det. Hvis handlingen mislykkes på serveren, gendannes UI'en til sin tidligere tilstand.

// 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;

  // Simuler interaktion med databasen
  console.log('Opdaterer element:', id, 'med navn:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler latenstid

  // Simuler fejl (til demonstrationsformål)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Kunne ikke opdatere element.');
  }

  console.log('Element opdateret succesfuldt!');
  return { name }; // Returner det opdaterede navn
}

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);

    // Opdater UI'en optimistisk
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      // Ved succes er opdateringen allerede afspejlet i UI'en via setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'Der opstod en fejl.');
      // Gendan UI'en ved fejl
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Nuværende navn: {itemName}

{errorMessage &&

{errorMessage}

}
); }

Forklaring:

Revalidering af data

Efter en Server Action har ændret data, kan det være nødvendigt at revalidere cachede data for at sikre, at UI'en afspejler de seneste ændringer. Next.js tilbyder flere måder at revalidere data på:

Her er et eksempel på revalidering af en sti efter oprettelse af et nyt element:

// 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;

  // Simuler interaktion med databasen
  console.log('Opretter element:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simuler latenstid

  console.log('Element oprettet succesfuldt!');

  revalidatePath('/items'); // Revalider /items-stien
}

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 || 'Der opstod en fejl.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Forklaring:

Bedste praksisser for Server Actions

For at maksimere fordelene ved Server Actions, bør du overveje følgende bedste praksisser:

Almindelige faldgruber og hvordan man undgår dem

Selvom Server Actions tilbyder talrige fordele, er der nogle almindelige faldgruber, man skal være opmærksom på:

Konklusion

Next.js 14 Server Actions giver en kraftfuld og effektiv måde at håndtere formularindsendelser og datamutationer direkte på serveren. Ved at følge de bedste praksisser, der er beskrevet i denne guide, kan du bygge robuste, sikre og performante webapplikationer. Omfavn Server Actions for at forenkle din kode, forbedre sikkerheden og den samlede brugeroplevelse. Når du integrerer disse principper, skal du overveje den globale indvirkning af dine udviklingsvalg. Sørg for, at dine formularer og datahåndteringsprocesser er tilgængelige, sikre og brugervenlige for forskellige internationale målgrupper. Dette engagement i inklusivitet vil ikke kun forbedre din applikations anvendelighed, men også udvide dens rækkevidde og effektivitet på globalt plan.