Български

Изчерпателно ръководство за сървърни действия в Next.js 14, обхващащо най-добрите практики за работа с формуляри, валидация на данни, сигурност и напреднали техники.

Сървърни действия в Next.js 14: Овладяване на най-добрите практики за работа с формуляри

Next.js 14 въвежда мощни функции за изграждане на производителни и лесни за употреба уеб приложения. Сред тях, сървърните действия (Server Actions) се открояват като трансформиращ начин за обработка на изпращане на формуляри и мутации на данни директно на сървъра. Това ръководство предоставя изчерпателен преглед на сървърните действия в Next.js 14, като се фокусира върху най-добрите практики за работа с формуляри, валидация на данни, сигурност и напреднали техники. Ще разгледаме практически примери и ще предоставим полезни съвети, които да ви помогнат да изграждате стабилни и мащабируеми уеб приложения.

Какво представляват сървърните действия в 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('Създаване на елемент:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Симулиране на забавяне

  console.log('Елементът е създаден успешно!');
}

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('Създаване на елемент:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Симулиране на забавяне

  console.log('Елементът е създаден успешно!');
}

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('Създаване на елемент:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Симулиране на забавяне

  console.log('Елементът е създаден успешно!');
}

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 Request Forgery (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('Създаване на елемент:', name, 'от потребител:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Симулиране на забавяне

  console.log('Елементът е създаден успешно!');
}

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('Актуализиране на елемент:', id, 'с име:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Симулиране на забавяне

  // Симулиране на неуспех (за демонстрационни цели)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Неуспешно актуализиране на елемента.');
  }

  console.log('Елементът е актуализиран успешно!');
  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}

}
); }

Обяснение:

Повторно валидиране на данни (Revalidating)

След като сървърно действие промени данни, може да се наложи да валидирате отново кешираните данни, за да сте сигурни, че потребителският интерфейс отразява най-новите промени. 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('Създаване на елемент:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Симулиране на забавяне

  console.log('Елементът е създаден успешно!');

  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 предоставят мощен и ефективен начин за обработка на изпращане на формуляри и мутации на данни директно на сървъра. Като следвате най-добрите практики, описани в това ръководство, можете да изграждате стабилни, сигурни и производителни уеб приложения. Възползвайте се от сървърните действия, за да опростите кода си, да подобрите сигурността и да повишите цялостното потребителско изживяване. Докато интегрирате тези принципи, помислете за глобалното въздействие на вашите решения за разработка. Уверете се, че вашите формуляри и процеси за обработка на данни са достъпни, сигурни и лесни за употреба за разнообразна международна аудитория. Този ангажимент към приобщаването не само ще подобри използваемостта на вашето приложение, но и ще разшири неговия обхват и ефективност в глобален мащаб.