[ ENGINEERING_GUIDE ][ NEXTJS ][ SERVER_ACTIONS ][ FORMULARZE ][ API_ROUTES ]

Next.js 15 Server Actions vs Route Handlers — formularze kontaktowe w 2026

10 czerwca 202610 min czytania
Autor: DevStudio.itStudio Web & AI

Kiedy użyć Server Actions, a kiedy zostać przy API Route? Walidacja, revalidatePath, CSRF i wzorzec z produkcyjnego formularza na Next.js 15.5.18.

READ_TIME: 10 MIN_COMPLEXITY: MED_
STAMP: VERIFIED_BY_DS_

TL;DR

Server Actions w Next.js 15 to domyślna droga do mutacji danych z formularza — bez osobnego endpointu REST, z wbudowaną ochroną przed prostymi atakami CSRF i naturalną integracją z React 19. Route Handlers (/api/...) nadal wygrywają, gdy potrzebujesz webhooków z zewnętrznych systemów, rate limitingu per IP, reCAPTCHA z weryfikacją po stronie serwera, integracji z CRM przez REST albo gdy formularz siedzi w dużym komponencie klienckim z analityką (gtag) po sukcesie. Na produkcyjnej stronie DevStudio.it formularz w page.tsx wysyła POST do /api/submissions — to świadomy wybór, nie „stary wzorzec”. Poniżej: porównanie, walidacja z Zod, revalidatePath, bezpieczeństwo i plan migracji krok po kroku.

Dla kogo to jest

  • Developerów budujących formularze leadowe na Next.js 15 (App Router)
  • Zespołów rozważających migrację z API Route na Server Action (lub odwrotnie)
  • Product ownerów, którzy chcą zrozumieć, dlaczego „prostszy kod” nie zawsze oznacza lepszy wybór
  • Firm z RODO, reCAPTCHA, wielojęzycznością (/pl, /en, /de) i analityką konwersji po submit

Fraza (SEO)

nextjs server actions formularz kontaktowy, server actions vs api route nextjs, revalidatePath formularz, walidacja formularza nextjs 15, csrf server actions nextjs

Dwa sposoby obsługi formularza w App Router

Next.js 15 oferuje dwie główne ścieżki mutacji po stronie serwera:

Aspekt Server Action Route Handler (/api/...)
Wywołanie action={submitContact} lub formAction fetch('/api/submissions', { method: 'POST' })
Format danych FormData (natywnie) lub serializowany obiekt JSON, multipart, dowolny
CSRF Wbudowane tokeny Next.js Musisz zabezpieczyć sam (lub polegać na SameSite cookies)
Webhooki zewnętrzne Nie — to wywołanie z Twojej strony Tak — standardowy REST endpoint
Cache / revalidacja revalidatePath, revalidateTag Ręcznie po mutacji lub osobna akcja
Testowanie Trudniejsze poza kontekstem RSC Postman, curl, integracje CI

Server Action to funkcja oznaczona 'use server', którą React wywołuje bezpośrednio z formularza. Next.js serializuje wynik, obsługuje POST z ukrytym polem akcji i — w przeciwieństwie do klasycznego API — nie wystawia publicznego URL, który każdy mógłby spamować curl-em (choć endpoint technicznie istnieje jako POST na bieżącą stronę).

Route Handler to plik route.ts w app/api/, znany z Pages Router i ekosystemu REST. Nadal jest domyślnym wyborem, gdy logika submit wykracza poza „zapisz do bazy i wyślij maila”.

Produkcyjny wzorzec: formularz w page.tsx + /api/submissions

W projekcie DevStudio.it (Next.js 15.5.18) formularz kontaktowy żyje w dużym komponencie klienckim src/app/[locale]/page.tsx. Po walidacji e-maila po stronie klienta i wykonaniu reCAPTCHA v3 wysyłany jest request:

const response = await fetch('/api/submissions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name, email, phone, projectType, budget, description,
    locale,
    recaptchaToken,
  }),
});

Po response.ok strona wysyła zdarzenia GA4 (generate_lead) i Google Ads (conversion_event_submit_lead_form), a następnie przekierowuje na stronę podziękowania z buforem event_timeout: 2000 — żeby tagi zdążyły przed nawigacją.

Route Handler w src/app/api/submissions/route.ts robi znacznie więcej niż prosty insert:

  • Rate limiting per IP (checkEmailRateLimit)
  • Weryfikacja reCAPTCHA z Google (siteverify, próg score 0.5)
  • Zapis do Prisma (submission.create)
  • Wysyłka maili transakcyjnych (admin + klient, szablony per locale)
  • Logowanie IP i User-Agent

To nie jest przypadek „zostaliśmy przy starym API”. To architektura, w której submit to mini-pipeline integracyjny, a sukces klienta musi być zsynchronizowany z analityką marketingową — co w Server Action wymaga albo przeniesienia całego flow do RSC, albo hybrydy.

Server Actions — jak to wygląda w Next.js 15

Minimalny przykład Server Action dla formularza kontaktowego:

// app/actions/contact.ts
'use server';

import { z } from 'zod';
import { revalidatePath } from 'next/cache';

const contactSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  description: z.string().min(10).max(5000),
  locale: z.enum(['pl', 'en', 'de']),
});

export type ContactFormState = {
  ok: boolean;
  message?: string;
  fieldErrors?: Record<string, string[]>;
};

export async function submitContact(
  _prev: ContactFormState,
  formData: FormData
): Promise<ContactFormState> {
  const parsed = contactSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    description: formData.get('description'),
    locale: formData.get('locale'),
  });

  if (!parsed.success) {
    return {
      ok: false,
      fieldErrors: parsed.error.flatten().fieldErrors,
    };
  }

  // ... zapis do bazy, mail, reCAPTCHA ...

  revalidatePath(`/${parsed.data.locale}`);
  return { ok: true, message: 'Wysłano pomyślnie' };
}

Formularz z React 19 useActionState (wcześniej useFormState):

'use client';

import { useActionState } from 'react';
import { submitContact } from '@/app/actions/contact';

export function ContactForm() {
  const [state, formAction, pending] = useActionState(submitContact, { ok: false });

  return (
    <form action={formAction}>
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="description" required />
      <input type="hidden" name="locale" value="pl" />
      <button type="submit" disabled={pending}>
        {pending ? 'Wysyłanie…' : 'Wyślij'}
      </button>
      {state.fieldErrors?.email && <p>{state.fieldErrors.email[0]}</p>}
      {state.ok && <p>{state.message}</p>}
    </form>
  );
}

Zalety tego podejścia:

  • Progressive enhancement — formularz działa bez JS (po submit pełna strona)
  • Brak ręcznego fetch i parsowania JSON w kliencie
  • Walidacja i mutacja w jednym miejscu na serwerze
  • pending z hooka — wbudowany stan ładowania

Walidacja: klient vs serwer

Reguła na 2026 rok jest niezmienna: walidacja klienta to UX, walidacja serwera to bezpieczeństwo.

W obecnym projekcie klient sprawdza format e-maila przed wysłaniem (validateEmail), a serwer w Route Handler weryfikuje wymagane pola i odrzuca request bez tokena reCAPTCHA. To poprawny podział.

W Server Action standardem jest Zod (lub Valibot) na wejściu:

const parsed = contactSchema.safeParse(Object.fromEntries(formData));

Dodatkowe wzorce:

  • Normalizacjaemail.trim().toLowerCase() przed zapisem
  • Honeypot — ukryte pole website; jeśli wypełnione, cichy sukces bez zapisu
  • Limit długości — ochrona przed payloadami 10 MB w polu description
  • Spójne komunikaty błędów per locale — mapowanie kodów Zod na tłumaczenia z messages/pl.json

Nigdy nie ufaj required w HTML jako jedynej barierze — boty i curl je omijają.

revalidatePath i odświeżanie UI

Po udanym zapisie Server Action często wywołuje:

import { revalidatePath } from 'next/cache';

revalidatePath('/pl/admin');           // konkretna ścieżka
revalidatePath('/pl', 'layout');       // cały segment layoutu
revalidateTag('submissions');          // jeśli lista używa fetch z tagiem

revalidatePath invaliduje cache RSC dla danej trasy — admin zobaczy nowy lead na liście bez hard refresh. W Route Handlerze musisz albo:

  • wywołać tę samą funkcję z shared modułu (import { revalidatePath } from 'next/cache' działa w route.ts),
  • albo polegać na SWR / React Query po stronie klienta z mutate() po sukcesie fetch.

Dla publicznego formularza kontaktowego revalidatePath rzadko jest potrzebny — użytkownik nie widzi listy zgłoszeń. Staje się kluczowy przy formularzach w panelu admina lub komentarzach na blogu.

Bezpieczeństwo: CSRF i Server Actions

Next.js Server Actions wysyłają POST z nagłówkiem i tokenem akcji, który framework weryfikuje. To ogranicza klasyczny CSRF z obcej domeny — atakujący nie wygeneruje poprawnego tokenu bez znajomości sekretu builda / origin.

Route Handlers nie mają tej ochrony domyślnie. Publiczny POST /api/submissions może być spamowany:

  • Rate limiting (jak w projekcie) — pierwsza linia obrony
  • reCAPTCHA v3 — druga linia
  • Weryfikacja Origin / Referer — dodatkowa warstwa
  • API key w nagłówku — gdy endpoint służy tylko Twojej aplikacji (nie publiczny formularz)

W Server Action reCAPTCHA nadal jest potrzebna — token generujesz w kliencie (grecaptcha.execute), przekazujesz w FormData, weryfikujesz w akcji tym samym kodem co w Route Handler.

Uwaga: Server Actions nie zastępują ochrony przed botnetami — tylko upraszczają model CSRF dla formularzy first-party.

Kiedy zostać przy Route Handler (API route)

Zostań przy /api/submissions (lub podobnym), gdy:

1. Zewnętrzne integracje i webhooki

Stripe, Calendly, Typeform wysyłają POST na stały URL. Route Handler to naturalne miejsce. Server Action nie ma publicznego adresu do skonfigurowania w panelu Stripe.

2. Klient już jest „client-heavy”

Formularz w page.tsx ma setki linii UI (animacje Framer Motion, sticky header, portfolio). Przeniesienie submit do Server Action wymaga albo wycięcia formularza do osobnego komponentu z useActionState, albo zostawienia fetch — oba podejścia są OK.

3. Analityka po sukcesie w przeglądarce

gtag('event', 'generate_lead') musi wykonać się w kliencie po HTTP 200. Server Action może zwrócić { ok: true }, a klient nadal wywoła gtag — hybryda działa. Ale jeśli chcesz redirect w akcji (redirect('/dziekujemy')), tracisz moment na callback konwersji Ads. Stąd obecny wzorzec: fetch → gtag z event_callback → redirect.

4. Testowanie i monitoring

Endpoint REST łatwo testujesz w Postmanie, load testach (k6) i logach reverse proxy (POST /api/submissions 429). Server Actions logują się jako POST na stronę — mniej czytelne w nginx access log.

5. Wielu konsumentów tego samego API

Mobile app, chatbot (/api/chatbot/submit), widget WordPress — jeden Route Handler, wiele klientów. W projekcie chatbot ma osobny endpoint, ale logika jest analogiczna.

Kiedy wybrać Server Action

  • Nowy, prosty formularz w RSC lub małym komponencie klienckim
  • Brak wymogu gtag przed redirect — thank-you page wystarczy
  • Chcesz progressive enhancement bez pisania osobnego API
  • Panel admina — edycja treści, status zgłoszenia, komentarze z revalidatePath
  • Mniejszy surface area — jeden plik akcji zamiast route.ts + typy odpowiedzi

Plan migracji: z API Route na Server Action (opcjonalnie)

Jeśli decydujesz się migrować formularz DevStudio-style:

  1. Wydziel logikę z route.ts do lib/submissions/create-submission.ts (pure async function).
  2. Utwórz app/actions/submit-contact.ts z 'use server' — wywołuje shared lib + revalidatePath('/pl/admin').
  3. Zostaw POST /api/submissions jako cienki wrapper wołający tę samą lib (backward compatibility, testy).
  4. Wyciągnij formularz z page.tsx do ContactFormSection.tsx z useActionState.
  5. Zachowaj blok gtag po state.ok w useEffect — analityka zostaje po stronie klienta.
  6. Przetestuj reCAPTCHA, rate limit i maile na stagingu.

Migracja nie musi być „big bang”. Shared lib + dwa entrypointy to dojrzały etap pośredni.

Progressive enhancement vs SPA submit

Server Action z natywnym <form action={...}> działa bez JavaScript. Obecny formularz z onSubmit, reCAPTCHA i fetch wymaga JS — użytkownik bez skryptów nie wyśle leada. Dla strony B2B software house to akceptowalne (99%+ ruchu ma JS), ale warto to świadomie zapisać w specyfikacji.

Jeśli RODO wymaga działania bez tracking cookies, Server Action + redirect na /dziekujemy daje lead w CRM bez gtag — spójne z „Tylko niezbędne” w banerze cookies.

FAQ

Czy Server Actions zastępują Route Handlers w Next.js 15?

Nie całkowicie. Actions są optymalne dla mutacji z Twojej aplikacji React. Route Handlers pozostają standardem dla webhooków, publicznego REST, uploadów multipart i integracji third-party. W jednym projekcie masz często obydwa.

Czy Server Action jest bezpieczniejszy niż API route?

Pod kątem CSRF — tak, domyślnie. Pod kątem spamu i botów — nie; nadal potrzebujesz rate limitu, CAPTCHA i walidacji serwerowej. Publiczny endpoint to publiczny endpoint — Action też może być masowo wywoływana z automatyzacji, jeśli ktoś zreverse-engineeruje token (trudniejsze niż curl na /api/..., ale nie niemożliwe).

Jak przekazać reCAPTCHA token w Server Action?

Wygeneruj token w 'use client' przed submit (grecaptcha.execute), wstaw jako <input type="hidden" name="recaptchaToken" value={token} /> lub dołącz do FormData w programmatic submit. W akcji serwerowej wywołaj ten sam fetch do google.com/recaptcha/api/siteverify co w Route Handler.

Czy mogę używać redirect() w Server Action po sukcesie?

Takimport { redirect } from 'next/navigation'. Pamiętaj: redirect w akcji ** przerywa** render i nie zwraca JSON do klienta. Jeśli potrzebujesz najpierw gtag, zrób redirect po stronie klienta (obecny wzorzec) albo thank-you page z tagiem konwersji w layout.

Czy revalidatePath jest potrzebny po formularzu kontaktowym?

Zwykle nie na stronie publicznej — użytkownik nie widzi cache listy. Tak w panelu admina, na blogu z komentarzami lub gdy po submit renderujesz „Twoje ostatnie zgłoszenia”.

JSON czy FormData w Server Action?

FormData — natywny format <form>. Dla pól złożonych możesz serializować JSON do jednego pola hidden. Unikaj przesyłania całego stanu React — tylko dane formularza.

Podsumowanie

Next.js 15.5.18 nie wymusza Server Actions na formularzach — daje lepszą alternatywę dla prostych mutacji z progresywnym ulepszeniem i wbudowanym CSRF. Produkcyjny formularz na DevStudio.it z fetch('/api/submissions'), reCAPTCHA, rate limitem, mailami i gtag po sukcesie to uzasadniony Route Handler, nie techniczny dług. Wybierz Server Action, gdy submit jest prosty i żyje blisko RSC; zostań przy API, gdy submit to pipeline integracyjny, webhooki lub ścisła synchronizacja z Google Ads. W obu przypadkach: Zod na serwerze, nigdy tylko HTML required, i wspólna lib jeśli planujesz migrację.

Chcesz wdrożyć formularz u siebie?

O autorze

Budujemy szybkie strony WWW, aplikacje web/mobile, chatboty AI i hosting — z naciskiem na SEO i konwersję.

Przydatne linki

Od teorii do produkcji — Branchly, hosting, opieka i realizacje.

PODOBA CI SIĘ NASZA ARCHITEKTURA MYŚLENIA? ZBUDUJMY COŚ RAZEM.

[ ROZPOCZNIJ_KONFIGURACJĘ_PROJEKTU ]