עברית

מדריך מקיף לפעולות שרת ב-Next.js 14, המכסה שיטות עבודה מומלצות לטיפול בטפסים, אימות נתונים, שיקולי אבטחה וטכניקות מתקדמות לבניית יישומי אינטרנט מודרניים.

פעולות שרת ב-Next.js 14: שליטה בשיטות עבודה מומלצות לטיפול בטפסים

גרסה 14 של Next.js מציגה תכונות עוצמתיות לבניית יישומי אינטרנט ביצועיסטיים וידידותיים למשתמש. בין אלה, פעולות שרת (Server Actions) בולטות כדרך מהפכנית לטפל בהגשת טפסים ובשינויי נתונים (mutations) ישירות בשרת. מדריך זה מספק סקירה מקיפה של פעולות שרת ב-Next.js 14, תוך התמקדות בשיטות עבודה מומלצות לטיפול בטפסים, אימות נתונים, אבטחה וטכניקות מתקדמות. נחקור דוגמאות מעשיות ונספק תובנות שימושיות שיעזרו לכם לבנות יישומים חזקים וניתנים להרחבה.

מהן פעולות שרת (Server Actions) ב-Next.js?

פעולות שרת הן פונקציות אסינכרוניות שרצות בשרת וניתן להפעילן ישירות מרכיבי React. הן מבטלות את הצורך בנתיבי API מסורתיים לטיפול בהגשות טפסים ושינויי נתונים, מה שמוביל לקוד פשוט יותר, אבטחה משופרת וביצועים משופרים. פעולות שרת הן רכיבי שרת של React (RSCs), כלומר הן מבוצעות בשרת, מה שמוביל לטעינת עמודים ראשונית מהירה יותר ו-SEO משופר.

היתרונות המרכזיים של פעולות שרת:

הגדרת פרויקט Next.js 14

לפני שנצלול לפעולות שרת, ודאו שיש לכם פרויקט Next.js 14 מוגדר. אם אתם מתחילים מאפס, צרו פרויקט חדש באמצעות הפקודה הבאה:

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

ודאו שהפרויקט שלכם משתמש במבנה התיקיות app כדי לנצל באופן מלא את רכיבי השרת והפעולות.

טיפול בסיסי בטפסים עם פעולות שרת

נתחיל עם דוגמה פשוטה: טופס ששולח נתונים ליצירת פריט חדש במסד נתונים. נשתמש בטופס פשוט עם שדה קלט וכפתור שליחה.

דוגמה: יצירת פריט חדש

ראשית, הגדירו פונקציית פעולת שרת בתוך רכיב ה-React שלכם. פונקציה זו תטפל בלוגיקת שליחת הטופס בשרת.

// 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)); // הדמיית השהיה

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

הסבר:

אימות נתונים

אימות נתונים הוא חיוני להבטחת שלמות הנתונים ולמניעת פגיעויות אבטחה. פעולות שרת מספקות הזדמנות מצוינת לבצע אימות בצד השרת. גישה זו מסייעת להפחית סיכונים הקשורים לאימות בצד הלקוח בלבד.

דוגמה: אימות נתוני קלט

שנו את פעולת השרת 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)); // הדמיית השהיה

  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)); // הדמיית השהיה

  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}

}
); }

הסבר:

שיקולי אבטחה

פעולות שרת משפרות את האבטחה על ידי הרצת קוד בשרת, אך עדיין חיוני לעקוב אחר שיטות עבודה מומלצות באבטחה כדי להגן על היישום שלכם מפני איומים נפוצים.

מניעת זיוף בקשות בין אתרים (CSRF)

התקפות CSRF מנצלות את האמון שיש לאתר בדפדפן של המשתמש. כדי למנוע התקפות CSRF, יש ליישם מנגנוני הגנה מפני CSRF.

Next.js מטפל אוטומטית בהגנת CSRF בעת שימוש בפעולות שרת. הפריימוורק יוצר ומאמת אסימון CSRF עבור כל שליחת טופס, ומבטיח שהבקשה מגיעה מהיישום שלכם.

טיפול באימות והרשאות משתמשים

ודאו שרק משתמשים מורשים יכולים לבצע פעולות מסוימות. יש ליישם מנגנוני אימות והרשאות כדי להגן על נתונים ופונקציונליות רגישים.

הנה דוגמה המשתמשת ב-NextAuth.js להגנה על פעולת שרת:

// 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)); // הדמיית השהיה

  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}

}
); }

הסבר:

חיטוי נתוני קלט

חטאו את נתוני הקלט כדי למנוע התקפות Cross-Site Scripting (XSS). התקפות XSS מתרחשות כאשר קוד זדוני מוזרק לאתר, ועלול לסכן את נתוני המשתמש או את פונקציונליות היישום.

השתמשו בספריות כמו DOMPurify או sanitize-html כדי לחטא קלט שסופק על ידי המשתמש לפני עיבודו בפעולות השרת שלכם.

טכניקות מתקדמות

כעת, לאחר שכיסינו את היסודות, בואו נחקור כמה טכניקות מתקדמות לשימוש יעיל בפעולות שרת.

עדכונים אופטימיים

עדכונים אופטימיים מספקים חווית משתמש טובה יותר על ידי עדכון מיידי של הממשק המשתמש כאילו הפעולה תצליח, עוד לפני שהשרת מאשר זאת. אם הפעולה נכשלת בשרת, הממשק המשתמש חוזר למצבו הקודם.

// 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)); // הדמיית השהיה

  // הדמיית כישלון (למטרות הדגמה)
  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);

    // עדכון אופטימי של הממשק המשתמש
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      // במקרה של הצלחה, העדכון כבר משתקף בממשק המשתמש דרך setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'אירעה שגיאה.');
      // החזרת הממשק המשתמש למצבו הקודם במקרה של שגיאה
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

שם נוכחי: {itemName}

{errorMessage &&

{errorMessage}

}
); }

הסבר:

אימות מחדש של נתונים (Revalidation)

לאחר שפעולת שרת משנה נתונים, ייתכן שתצטרכו לאמת מחדש נתונים שנשמרו במטמון (cache) כדי להבטיח שהממשק המשתמש משקף את השינויים האחרונים. Next.js מספק מספר דרכים לאמת נתונים מחדש:

הנה דוגמה לאימות מחדש של נתיב לאחר יצירת פריט חדש:

// 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)); // הדמיית השהיה

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

  revalidatePath('/items'); // אימות מחדש של הנתיב /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}

}
); }

הסבר:

שיטות עבודה מומלצות עבור פעולות שרת

כדי למקסם את היתרונות של פעולות שרת, שקלו את שיטות העבודה המומלצות הבאות:

מכשולים נפוצים וכיצד להימנע מהם

בעוד שפעולות שרת מציעות יתרונות רבים, ישנם כמה מכשולים נפוצים שכדאי להיות מודעים אליהם:

סיכום

פעולות השרת ב-Next.js 14 מספקות דרך עוצמתית ויעילה לטפל בהגשת טפסים ושינויי נתונים ישירות בשרת. על ידי מעקב אחר שיטות העבודה המומלצות המתוארות במדריך זה, תוכלו לבנות יישומי אינטרנט חזקים, מאובטחים וביצועיסטיים. אמצו את פעולות השרת כדי לפשט את הקוד שלכם, לשפר את האבטחה ולשפר את חווית המשתמש הכוללת. בזמן שאתם משלבים עקרונות אלה, שקלו את ההשפעה הגלובלית של בחירות הפיתוח שלכם. ודאו שהטפסים ותהליכי הטיפול בנתונים שלכם נגישים, מאובטחים וידידותיים למשתמש עבור קהלים בינלאומיים מגוונים. מחויבות זו להכללה לא רק תשפר את שימושיות היישום שלכם אלא גם תרחיב את טווח ההגעה והיעילות שלו בקנה מידה עולמי.