Ελληνικά

Ένας αναλυτικός οδηγός για τα Server Actions του Next.js 14, που καλύπτει βέλτιστες πρακτικές χειρισμού φορμών, επικύρωση δεδομένων, θέματα ασφάλειας και προηγμένες τεχνικές για τη δημιουργία σύγχρονων web εφαρμογών.

Next.js 14 Server Actions: Κατακτήστε τις Βέλτιστες Πρακτικές στον Χειρισμό Φορμών

Το Next.js 14 εισάγει ισχυρά χαρακτηριστικά για τη δημιουργία αποδοτικών και φιλικών προς τον χρήστη web εφαρμογών. Μεταξύ αυτών, τα Server Actions ξεχωρίζουν ως ένας μετασχηματιστικός τρόπος για τον χειρισμό υποβολών φορμών και μεταλλάξεων δεδομένων (data mutations) απευθείας στον server. Αυτός ο οδηγός παρέχει μια ολοκληρωμένη επισκόπηση των Server Actions στο Next.js 14, εστιάζοντας στις βέλτιστες πρακτικές για τον χειρισμό φορμών, την επικύρωση δεδομένων, την ασφάλεια και τις προηγμένες τεχνικές. Θα εξερευνήσουμε πρακτικά παραδείγματα και θα παρέχουμε εφαρμόσιμες γνώσεις για να σας βοηθήσουμε να δημιουργήσετε στιβαρές και επεκτάσιμες web εφαρμογές.

Τι είναι τα Next.js Server Actions;

Τα Server Actions είναι ασύγχρονες συναρτήσεις που εκτελούνται στον server και μπορούν να κληθούν απευθείας από React components. Εξαλείφουν την ανάγκη για παραδοσιακά API routes για τον χειρισμό υποβολών φορμών και μεταλλάξεων δεδομένων, οδηγώντας σε απλοποιημένο κώδικα, βελτιωμένη ασφάλεια και ενισχυμένη απόδοση. Τα Server Actions είναι React Server Components (RSCs), που σημαίνει ότι εκτελούνται στον server, οδηγώντας σε ταχύτερους αρχικούς χρόνους φόρτωσης σελίδας και βελτιωμένο SEO.

Βασικά Οφέλη των Server Actions:

Ρύθμιση του Next.js 14 Project σας

Πριν ασχοληθείτε με τα Server Actions, βεβαιωθείτε ότι έχετε ένα ρυθμισμένο project Next.js 14. Αν ξεκινάτε από την αρχή, δημιουργήστε ένα νέο project χρησιμοποιώντας την ακόλουθη εντολή:

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

Βεβαιωθείτε ότι το project σας χρησιμοποιεί τη δομή καταλόγου app για να εκμεταλλευτείτε πλήρως τα Server Components και Actions.

Βασικός Χειρισμός Φορμών με Server Actions

Ας ξεκινήσουμε με ένα απλό παράδειγμα: μια φόρμα που υποβάλλει δεδομένα για τη δημιουργία ενός νέου αντικειμένου σε μια βάση δεδομένων. Θα χρησιμοποιήσουμε μια απλή φόρμα με ένα πεδίο εισαγωγής και ένα κουμπί υποβολής.

Παράδειγμα: Δημιουργία Νέου Αντικειμένου

Πρώτα, ορίστε μια συνάρτηση Server Action μέσα στο React component σας. Αυτή η συνάρτηση θα χειριστεί τη λογική υποβολής της φόρμας στον server.

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

import { useState } from 'react';

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

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

  // Προσομοίωση αλληλεπίδρασης με τη βάση δεδομένων
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Προσομοίωση καθυστέρησης (latency)

  console.log('Item created successfully!');
}

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

  return (
    
); }

Επεξήγηση:

Επικύρωση Δεδομένων

Η επικύρωση δεδομένων είναι ζωτικής σημασίας για τη διασφάλιση της ακεραιότητας των δεδομένων και την πρόληψη ευπαθειών ασφαλείας. Τα Server Actions παρέχουν μια εξαιρετική ευκαιρία για την εκτέλεση επικύρωσης από την πλευρά του server. Αυτή η προσέγγιση βοηθά στη μείωση των κινδύνων που σχετίζονται μόνο με την επικύρωση από την πλευρά του client.

Παράδειγμα: Επικύρωση Δεδομένων Εισόδου

Τροποποιήστε το Server Action createItem για να συμπεριλάβετε λογική επικύρωσης.

// 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('Το όνομα του αντικειμένου πρέπει να έχει τουλάχιστον 3 χαρακτήρες.');
  }

  // Προσομοίωση αλληλεπίδρασης με τη βάση δεδομένων
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Προσομοίωση καθυστέρησης (latency)

  console.log('Item created successfully!');
}

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 || 'Παρουσιάστηκε σφάλμα.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Επεξήγηση:

Χρήση Βιβλιοθηκών Επικύρωσης

Για πιο σύνθετα σενάρια επικύρωσης, εξετάστε τη χρήση βιβλιοθηκών επικύρωσης όπως:

Ακολουθεί ένα παράδειγμα με χρήση του Zod:

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

export const CreateItemSchema = z.object({
  name: z.string().min(3, 'Το όνομα του αντικειμένου πρέπει να έχει τουλάχιστον 3 χαρακτήρες.'),
});
// 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 };
  }

  // Προσομοίωση αλληλεπίδρασης με τη βάση δεδομένων
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Προσομοίωση καθυστέρησης (latency)

  console.log('Item created successfully!');
}

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 || 'Παρουσιάστηκε σφάλμα.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Επεξήγηση:

Θέματα Ασφάλειας

Τα Server Actions ενισχύουν την ασφάλεια εκτελώντας τον κώδικα στον server, αλλά εξακολουθεί να είναι ζωτικής σημασίας η τήρηση των βέλτιστων πρακτικών ασφαλείας για την προστασία της εφαρμογής σας από κοινές απειλές.

Πρόληψη Cross-Site Request Forgery (CSRF)

Οι επιθέσεις CSRF εκμεταλλεύονται την εμπιστοσύνη που έχει ένας ιστότοπος στο πρόγραμμα περιήγησης ενός χρήστη. Για την πρόληψη επιθέσεων CSRF, εφαρμόστε μηχανισμούς προστασίας CSRF.

Το Next.js χειρίζεται αυτόματα την προστασία CSRF κατά τη χρήση των Server Actions. Το framework δημιουργεί και επικυρώνει ένα CSRF token για κάθε υποβολή φόρμας, διασφαλίζοντας ότι το αίτημα προέρχεται από την εφαρμογή σας.

Χειρισμός Ελέγχου Ταυτότητας και Εξουσιοδότησης Χρήστη

Βεβαιωθείτε ότι μόνο εξουσιοδοτημένοι χρήστες μπορούν να εκτελέσουν συγκεκριμένες ενέργειες. Εφαρμόστε μηχανισμούς ελέγχου ταυτότητας και εξουσιοδότησης για την προστασία ευαίσθητων δεδομένων και λειτουργιών.

Ακολουθεί ένα παράδειγμα χρήσης του NextAuth.js για την προστασία ενός 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('Μη εξουσιοδοτημένη πρόσβαση');
  }

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

  // Προσομοίωση αλληλεπίδρασης με τη βάση δεδομένων
  console.log('Creating item:', name, 'by user:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Προσομοίωση καθυστέρησης (latency)

  console.log('Item created successfully!');
}

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 || 'Παρουσιάστηκε σφάλμα.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Επεξήγηση:

Εξυγίανση Δεδομένων Εισόδου (Sanitizing)

Εξυγιάνετε τα δεδομένα εισόδου για την πρόληψη επιθέσεων Cross-Site Scripting (XSS). Οι επιθέσεις XSS συμβαίνουν όταν εισάγεται κακόβουλος κώδικας σε έναν ιστότοπο, θέτοντας δυνητικά σε κίνδυνο τα δεδομένα του χρήστη ή τη λειτουργικότητα της εφαρμογής.

Χρησιμοποιήστε βιβλιοθήκες όπως το DOMPurify ή το sanitize-html για την εξυγίανση των δεδομένων που παρέχονται από τον χρήστη πριν από την επεξεργασία τους στα Server Actions σας.

Προηγμένες Τεχνικές

Τώρα που καλύψαμε τα βασικά, ας εξερευνήσουμε μερικές προηγμένες τεχνικές για την αποτελεσματική χρήση των Server Actions.

Αισιόδοξες Ενημερώσεις (Optimistic Updates)

Οι αισιόδοξες ενημερώσεις παρέχουν μια καλύτερη εμπειρία χρήστη, ενημερώνοντας αμέσως το UI σαν να πρόκειται να πετύχει η ενέργεια, ακόμη και πριν το επιβεβαιώσει ο server. Εάν η ενέργεια αποτύχει στον server, το UI επανέρχεται στην προηγούμενη του κατάσταση.

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

  // Προσομοίωση αλληλεπίδρασης με τη βάση δεδομένων
  console.log('Updating item:', id, 'with name:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Προσομοίωση καθυστέρησης (latency)

  // Προσομοίωση αποτυχίας (για λόγους επίδειξης)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Η ενημέρωση του αντικειμένου απέτυχε.');
  }

  console.log('Item updated successfully!');
  return { name }; // Επιστροφή του ενημερωμένου ονόματος
}

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

    // Αισιόδοξη ενημέρωση του UI
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      // Αν η ενέργεια είναι επιτυχής, τότε η ενημέρωση έχει ήδη εφαρμοστεί στο UI μέσω του setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'Παρουσιάστηκε σφάλμα.');
      // Επαναφορά του UI σε περίπτωση σφάλματος
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Τρέχον Όνομα: {itemName}

{errorMessage &&

{errorMessage}

}
); }

Επεξήγηση:

Επανεπικύρωση Δεδομένων (Revalidating)

Αφού ένα Server Action τροποποιήσει δεδομένα, μπορεί να χρειαστεί να επανεπικυρώσετε τα αποθηκευμένα στην κρυφή μνήμη (cached) δεδομένα για να διασφαλίσετε ότι το UI αντικατοπτρίζει τις τελευταίες αλλαγές. Το Next.js παρέχει διάφορους τρόπους για την επανεπικύρωση δεδομένων:

Ακολουθεί ένα παράδειγμα επανεπικύρωσης ενός path μετά τη δημιουργία ενός νέου αντικειμένου:

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

  // Προσομοίωση αλληλεπίδρασης με τη βάση δεδομένων
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Προσομοίωση καθυστέρησης (latency)

  console.log('Item created successfully!');

  revalidatePath('/items'); // Επανεπικύρωση του path /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 || 'Παρουσιάστηκε σφάλμα.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Επεξήγηση:

Βέλτιστες Πρακτικές για τα Server Actions

Για να μεγιστοποιήσετε τα οφέλη των Server Actions, λάβετε υπόψη τις ακόλουθες βέλτιστες πρακτικές:

Συνήθεις Παγίδες και Πώς να τις Αποφύγετε

Ενώ τα Server Actions προσφέρουν πολλά πλεονεκτήματα, υπάρχουν μερικές συνηθισμένες παγίδες που πρέπει να γνωρίζετε:

Συμπέρασμα

Τα Server Actions του Next.js 14 παρέχουν έναν ισχυρό και αποτελεσματικό τρόπο για τον χειρισμό υποβολών φορμών και μεταλλάξεων δεδομένων απευθείας στον server. Ακολουθώντας τις βέλτιστες πρακτικές που περιγράφονται σε αυτόν τον οδηγό, μπορείτε να δημιουργήσετε στιβαρές, ασφαλείς και αποδοτικές web εφαρμογές. Υιοθετήστε τα Server Actions για να απλοποιήσετε τον κώδικά σας, να ενισχύσετε την ασφάλεια και να βελτιώσετε τη συνολική εμπειρία χρήστη. Καθώς ενσωματώνετε αυτές τις αρχές, σκεφτείτε τον παγκόσμιο αντίκτυπο των επιλογών ανάπτυξής σας. Διασφαλίστε ότι οι φόρμες και οι διαδικασίες χειρισμού δεδομένων σας είναι προσβάσιμες, ασφαλείς και φιλικές προς τον χρήστη για ποικίλα διεθνή κοινά. Αυτή η δέσμευση στη συμπεριληπτικότητα όχι μόνο θα βελτιώσει τη χρηστικότητα της εφαρμογής σας, αλλά θα διευρύνει επίσης την εμβέλεια και την αποτελεσματικότητά της σε παγκόσμια κλίμακα.