Przejdź do treści

Frontend w Next.js i React: gdzie dziś przebiega granica serwer–klient

Jak budować frontend w Next.js 16: wybór routera, miejsce pobierania danych, stan w URL, formularze, wydajność i testy. Decyzje z uzasadnieniem.

Maciej Sala

Founder StriveLab

13 min czytaniaAktualizacja

Co dziś znaczy „frontend” w Next.js?

Frontend odpowiada za doświadczenie użytkownika, ale nie cały jego kod musi wykonywać się w przeglądarce. Serwer może przygotować listę produktów, opis usługi i układ strony. Klient obsłuży otwieranie panelu, przeciąganie elementów czy roboczą wartość pola wyszukiwania.

W App Routerze strony i layouty są domyślnie komponentami serwerowymi. Server Component nie oznacza jednak renderowania przy każdym żądaniu. Może wykonać się podczas prerenderowania albo obsługi żądania — zależnie od danych i konfiguracji cache.

WarstwaGdzie się wykonujeCo o tym decyduje
Server ComponentNa serwerze, podczas prerenderowania lub żądaniaUżycie w drzewie serwerowym oraz wymagania danych
Client ComponentPrzy pierwszym wejściu zwykle także na serwerze dla HTML; następnie w przeglądarceGranica modułów wyznaczona przez 'use client'
Obsługa kliknięć, efekty, API przeglądarkiW przeglądarceInterakcja, cykl życia i dostęp do środowiska klienta
Server Function użyta jako akcjaNa serwerze po wywołaniuSerwerowa funkcja udostępniona przez framework
CSS i układ responsywnyW przeglądarceReguły stylów oraz możliwości i rozmiar obszaru wyświetlania

'use client' wyznacza granicę w grafie importów. Importowane przez ten moduł komponenty i zależności mogą wejść do paczki klienta, nawet jeśli same nie zawierają dyrektywy. Dlatego dodanie jej do dużego kontenera strony ma inne konsekwencje niż wydzielenie małego przycisku.

Nie oznacza to, że wszystko wizualnie zagnieżdżone w komponencie klienckim musi stać się klienckie. Serwerowy rodzic może przekazać przygotowaną zawartość przez children. Liczy się sposób kompozycji i importowania, a nie samo położenie elementu w JSX.

Komponent kliencki nie oznacza „bez SSR”. Jego początkowy HTML może powstać na serwerze, a hydratacja dołączy obsługę interakcji. Pierwszy render w obu środowiskach musi być zgodny. Czas, losowe wartości czy odczyt ustawień przeglądarki podczas renderowania mogą tę zgodność naruszyć. Hydration mismatch ma również inne przyczyny, np. niepoprawne zagnieżdżenie HTML; nie jest automatycznym dowodem błędnej architektury.

Praktyczny podział dla karty produktu: opis i parametry na serwerze, wybór wariantu i galeria po stronie klienta. Wpływ takiej decyzji na widoczność treści oraz koszt przeglądarki rozwija tekst o React Server Components, SEO i performance.

Router i model renderowania

W nowym projekcie opartym na opisanym modelu naturalnym wyborem jest App Router. Umożliwia korzystanie z RSC, zagnieżdżonych layoutów i granic oczekiwania na dane. Działająca aplikacja w Pages Routerze wymaga jednak innej kalkulacji: co konkretnie zespół zyska po migracji?

SytuacjaKierunek decyzjiUzasadnienie
Nowy projekt z treścią i interaktywnymi fragmentamiApp RouterPozwala zaplanować granicę serwer–klient od początku
Stabilny produkt w Pages RouterzeUtrzymanie albo migracja etapamiPrzepisanie routingu musi rozwiązywać konkretny problem
Rozbudowa o odrębny obszar produktuPilotaż App Routera w wybranych trasachOgranicza zakres jednorazowej zmiany
Strona łączy wspólną treść i wolne dane dynamiczneOcena streamingu oraz PPRUżytkownik może otrzymać część widoku wcześniej

App i Pages Router mogą współistnieć podczas migracji, ale nie powinny obsługiwać tej samej ścieżki. Kosztem zmiany są także providery, pobieranie danych, obsługa błędów i testy nawigacji. Kryteria wyboru opisuje porównanie App Routera i Pages Routera, a kolejność prac — przewodnik migracji do App Routera.

Streaming pozwala przesyłać gotowe części odpowiedzi, gdy pozostałe jeszcze czekają na dane. Granica Suspense powinna odpowiadać fragmentowi, który ma sens samodzielnie: rekomendacjom pod produktem, a nie przypadkowemu opakowaniu całej strony. Streaming skraca oczekiwanie na część widoku, lecz nie przyspiesza samego zapytania. Szczegóły podziału znajdziesz w artykule o streamingu i Suspense w Next.js.

Partial Prerendering łączy przygotowaną wcześniej część strony z fragmentami uzupełnianymi dynamicznie. W Next.js 16 jest powiązany z modelem Cache Components, włączanym przez cacheComponents: true. Starszych przykładów z experimental.ppr nie należy przenosić bez zmian. Wyjaśnienie PPR warto czytać z uwzględnieniem wersji projektu i sposobu konfiguracji cache.

Osobna decyzja dotyczy interfejsów, w których adres reprezentuje panel lub modal. Parallel i Intercepting Routes mają sens, gdy trzeba zachować kontekst poprzedniego widoku i zapewnić bezpośrednie wejście pod URL. Zwykły lokalny modal nie potrzebuje od razu takiej architektury.

Jeśli projekt startował na starszej wersji frameworka, porównanie Next.js 15 i 14 pomaga zrozumieć wcześniejsze zmiany. Nie zastępuje jednak instrukcji aktualizacji do wersji 16.

Gdzie pobierać dane

Pytanie brzmi: kto potrzebuje danych i kiedy mają się zmienić? Opis kategorii potrzebny do pierwszego renderowania to inny przypadek niż lista zadań odświeżana podczas pracy kilku użytkowników.

Jeżeli dane są potrzebne serwerowemu widokowi, pobierz je w warstwie serwerowej. RSC może korzystać z bazy lub zewnętrznego API bez dodatkowego wywoływania własnego endpointu HTTP wyłącznie po to, aby wrócić do tej samej logiki. Niezależne zapytania planuj współbieżnie: przeniesienie kilku kolejnych await na serwer nie usuwa kaskady oczekiwania.

Cache jest osobną decyzją. Sam Server Component nie określa, jak długo wynik pozostaje aktualny. Trzeba ustalić akceptowalną nieświeżość, moment unieważnienia i to, czy dane są wspólne, czy przypisane użytkownikowi.

TanStack Query lub SWR zaczyna uzasadniać swoją obecność przy odświeżaniu w tle, współdzieleniu wyników między interaktywnymi widokami czy częstych zmianach danych. RSC i cache kliencki mogą współpracować: serwer dostarcza pierwszy widok, a klient obsługuje dalszy cykl życia danych.

Najczęstszy nadmiar to dołożenie React Query do treści, która przez całą wizytę pozostaje taka sama. Powstaje druga warstwa zarządzania danymi, chociaż interfejs nie potrzebuje jej możliwości. Z drugiej strony wymuszanie wyłącznie serwerowego modelu w intensywnie interaktywnym panelu też potrafi komplikować produkt.

Kryteria doboru biblioteki i ograniczenia pobierania danych w efekcie omawia porównanie React Query, SWR i useEffect.

Stan, który nie należy do serwera

Stan aplikacji nie jest jednym workiem. Inne wymagania ma otwarty tooltip, inne wybrana strona wyników, a jeszcze inne status płatności.

Rodzaj stanuGłówne miejsce przechowywaniaDlaczego
Filtry, sortowanie, paginacjaURLWidok można odtworzyć, udostępnić i przywrócić przyciskiem Wstecz
Otwarte menu, fokus, roboczy tekstStan lokalny komponentuTo chwilowy kontekst interakcji
Dane pobrane ze źródłaSerwer lub świadomie zarządzany cache klientaIch aktualność wynika z cyklu życia danych
Uprawnienia, cena rozliczeniowa, wynik zapisuSerwerKlient nie może być źródłem zaufania

W wyszukiwarce wpisywany tekst może chwilowo żyć lokalnie, a zatwierdzone zapytanie trafić do URL. Debounce ogranicza częstotliwość aktualizacji. Trzeba również ustalić, które zmiany dopisują wpis do historii, a które tylko zastępują bieżący adres. Inaczej przycisk Wstecz zacznie odtwarzać każdą wpisaną literę.

Parametry URL nie wykluczają indeksowania. Dlatego techniczna wygoda zapisywania filtrów wymaga decyzji SEO: które kombinacje tworzą wartościowe strony, a które są jedynie wariantami użytkowymi. Nie należy automatycznie kierować każdego adresu kanonicznego do kategorii bazowej, jeśli podstrony reprezentują odrębną treść. Trzeba też ograniczyć generowanie niepotrzebnych kombinacji w linkowaniu.

Spójne zachowanie adresu i interfejsu opisuje wyszukiwarka z filtrami, searchParams i stanem w URL. Dopiero powtarzalną logikę warto wydzielać do własnych hooków z typami generycznymi. Abstrakcja ma utrwalać dobry model stanu, a nie ukrywać jego duplikację.

Formularze i mutacje

React 19 Actions pozwalają organizować zmianę danych wokół operacji i jej wyniku. W Next.js Server Actions umożliwiają wykonanie zapisu na serwerze. useActionState pomaga przedstawić wynik i stan oczekiwania, a useOptimistic — pokazać przewidywany rezultat przed zakończeniem operacji.

Punkt wyjścia dla prostego formularza to pola HTML, akcja oraz walidacja serwerowa. Walidacja w przeglądarce skraca drogę do poprawienia błędu, lecz nie zastępuje kontroli na serwerze. Każda operacja zapisu musi ponownie sprawdzić użytkownika i jego prawo do modyfikowanego zasobu.

Po mutacji trzeba odpowiedzieć na jeszcze jedno pytanie: skąd reszta interfejsu dowie się o zmianie? Jeśli widok korzysta z cache serwerowego i klienckiego, odświeżenie tylko jednej warstwy może pozostawić sprzeczne informacje. Sposób unieważnienia danych należy do projektu mutacji, a nie do późniejszego dopracowania UI.

Formularz z Server Action przekazaną do action, renderowany w komponencie serwerowym, może obsługiwać wysłanie bez załadowanego JavaScriptu. Nie oznacza to, że dowolny formularz z Actions ma tę właściwość. Własne zdarzenia, pola dostępne dopiero po interakcji i kliencka nawigacja między krokami mogą tworzyć zależność od JS.

Stan kliencki nadal jest potrzebny w rozbudowanym kreatorze: do warunkowych pól, podglądu, zależności między krokami i zachowania wersji roboczej. Dobór mechanizmów pokazuje tekst o Actions, useOptimistic i useActionState, a projekt dłuższej ścieżki — poradnik o formularzu wielokrokowym w Next.js.

Warstwa UI

Design system powinien określać nie tylko wygląd przycisku, lecz także jego zachowanie podczas oczekiwania, sposób komunikowania błędu i obsługę klawiatury. To kontrakt między częściami aplikacji i osobami, które je rozwijają.

W architekturze RSC kontrakt obejmuje także zależności. Prosty nagłówek czy kontener nie powinien wymagać klienta tylko dlatego, że korzysta ze wspólnego pakietu UI. Interaktywne elementy warto wydzielić tak, aby import zwykłego elementu prezentacyjnego nie sprowadzał niepotrzebnej logiki przeglądarkowej. Taką organizację rozwija design system z Tailwind, CVA i Radix UI.

Trzy przypadki szybko weryfikują jakość tego kontraktu:

  • Tabela danych: sortowanie i zaznaczanie wymagają interakcji, ale nie uzasadniają pobierania całej bazy do przeglądarki. Przy dużym zbiorze najpierw ustal miejsce filtrowania i paginacji; później wybierz mechanikę tabel z TanStack Table.
  • Animacje: przejście ma wyjaśniać zmianę, respektować ograniczenie ruchu i nie opóźniać zadania. Bibliotekę dodawaj do fragmentów, które jej potrzebują, zgodnie z zasadami wydajnych animacji Framer Motion.
  • Responsywność: układ zależny od szerokości zwykle należy do CSS. Odczytywanie window.innerWidth do wyboru podstawowej wersji strony dokłada JavaScript i komplikuje zgodność pierwszego renderowania. Punktem wyjścia pozostaje RWD, mobile first i media queries.

Wydajność: co naprawdę kosztuje

Optymalizacja zaczyna się od ustalenia, na co czeka użytkownik. Wolny serwer, duży obraz i przeciążony główny wątek przeglądarki wymagają innych działań. W pracy nad samym frontendem warto jednak przyjąć kolejność, która chroni przed dopieszczaniem zbędnego kodu.

  1. Sprawdź granice klienta i ciężkie importy. Czy cała strona potrzebuje hydratacji swoich interaktywnych komponentów, czy wystarczy kilka wyodrębnionych fragmentów?
  2. Zmierz zasoby dla konkretnej trasy. Oddziel początkowy JavaScript od kodu ładowanego później i sprawdź faktyczny transfer oraz czas wykonania.
  3. Usuń zbędną pracę i kaskady oczekiwania. Oceń duplikowane zapytania, wielkość przekazywanych danych, obrazy, fonty i skrypty zewnętrzne.
  4. Dopiero potem profiluj rendery. Znajdź kosztowne obliczenie, zbyt szeroką subskrypcję lub efekt wywołujący kolejną aktualizację.

RSC nie oznacza zerowego transferu. Do przeglądarki nadal trafiają HTML, dane protokołu RSC i kod komponentów klienckich. Przekazanie tysięcy rekordów do niewielkiego widgetu potrafi zniwelować korzyść z ograniczenia liczby komponentów klienckich. Metody pomiaru przedstawia analiza i zmniejszanie paczki JavaScript w Next.js.

React Compiler zmienia opłacalność ręcznego dodawania useMemo, useCallback i memo. Gdy jest skonfigurowany i obejmuje dany kod, automatyzuje część memoizacji. Sama obecność Reacta 19 nie dowodzi jednak, że kompilator działa w projekcie. Nie usuwa on też kosztu sieci ani nadmiarowych zależności.

W nowym kodzie warto korzystać z jego optymalizacji, a ręczną kontrolę zachować dla uzasadnionych przypadków. Istniejącej memoizacji nie usuwaj hurtowo bez sprawdzenia rezultatów. Ten podział rozwija artykuł o React Compilerze i przyszłości useMemo oraz useCallback.

Sama liczba renderów również nie jest wynikiem wydajnościowym. Dodatkowe wywołania w trybie deweloperskim nie muszą odpowiadać zachowaniu produkcji. Istotny jest koszt i wpływ na interakcję — temu służy debugowanie niepotrzebnego renderowania w React.

Efekt zmian sprawdzaj na produkcyjnym buildzie oraz, jeśli masz takie dane, w rzeczywistych wizytach. LCP opisuje pojawienie się największego elementu treści, INP reakcję na interakcje, a CLS stabilność układu. Wynik laboratoryjny pomaga diagnozować, lecz nie zastępuje doświadczeń użytkowników.

Typy i bezpieczeństwo po stronie klienta

Typy mają uniemożliwiać sprzeczne stany

TypeScript daje najwięcej, gdy opisuje reguły, a nie tylko kształt obiektu. Unia rozróżniana dla wyniku operacji może wykluczyć sytuację, w której komponent jednocześnie pokazuje sukces i błąd. Precyzyjne warianty propsów ograniczają nieprawidłowe konfiguracje, a generyki zachowują relacje między argumentem i wynikiem.

Typ nie waliduje danych z zewnątrz. Parametry URL, odpowiedź API i dane formularza wymagają kontroli w runtime. Rzutowanie odpowiedzi na oczekiwany interfejs nie potwierdza jej poprawności.

Klient nie jest miejscem na sekret ani decyzję o uprawnieniach

Kod wykonywany w przeglądarce i przesłane do niej dane są dostępne użytkownikowi. Klucz prywatnego API musi pozostać na serwerze. Dotyczy to również danych: komponent serwerowy nie ochroni sekretu, jeśli przekaże go w propsach do klienta. Do UI wysyłaj wyłącznie pola potrzebne do jego działania.

W aplikacji Next.js z własnym zapleczem rozsądnym punktem wyjścia jest sesja oparta na cookie HttpOnly, z Secure i właściwym SameSite. Token dostępu do zewnętrznego API może pozostać na serwerze, a przeglądarka otrzymać identyfikator sesji. HttpOnly utrudnia odczyt cookie przez JavaScript, ale nie usuwa skutków XSS ani wszystkich zagrożeń związanych z wykonywaniem żądań.

localStorage nie powinien być odruchowym schowkiem na długowieczne tokeny. Samodzielna SPA korzystająca z zewnętrznego dostawcy to odrębny przypadek: sposób logowania i odświeżania musi odpowiadać architekturze oraz zaleceniom dostawcy. Warianty opisuje poradnik o tokenach, odświeżaniu sesji i autentykacji w React.

Ukrycie przycisku w UI nie stanowi autoryzacji. Server Action oraz endpoint muszą sprawdzać uprawnienia przy każdym wrażliwym wywołaniu, niezależnie od tego, z jakiej strony użytkownik miał do nich trafić.

Jak to testować

Granica serwer–klient pomaga również rozdzielić testy. Funkcja ustalająca dostępne warianty produktu nie potrzebuje uruchomionej przeglądarki. Ścieżka „zmień wariant, zapisz, odśwież stronę” już wymaga sprawdzenia współpracy kilku warstw.

ZakresRodzaj testuCo warto potwierdzić
Reguły domenowe i transformacjeJednostkowyPoprawność wyniku i przypadki brzegowe
Interaktywny komponentKomponentowyZdarzenia, dostępność i komunikaty
Dostęp do danych i mutacjeIntegracyjnyWalidację, autoryzację i skutki zapisu
Asynchroniczna strona RSCE2EWynik rzeczywistego renderowania i współpracę z routingiem

Wsparcie asynchronicznych Server Components w popularnych narzędziach jednostkowych jest ograniczone. Dokumentacja Next.js dla Jest zaleca w tym obszarze E2E. Nie oznacza to rezygnacji z testowania logiki serwerowej: można ją wydzielić i sprawdzać niezależnie.

Mockuj zewnętrzną granicę wtedy, gdy chcesz odizolować konkretną regułę. Jeżeli celem jest sprawdzenie autoryzacji, zastąpienie jej zawsze pozytywnym mockiem odbiera testowi sens. Dla głównej ścieżki warto potwierdzić zapis, ponowne wejście i widoczność aktualnych danych. Dobór zakresu opisuje testowanie Server Components: mocki, logika i E2E.

Lista kontrolna przed wdrożeniem

  • Każda granica 'use client' ma uzasadnienie w interakcji, stanie lub zależności od przeglądarki; sprawdzono jej ciężkie importy.
  • Pierwsze wejście i nawigacja działają na produkcyjnym buildzie bez błędów hydratacji, również przy innych ustawieniach języka i czasu.
  • Sekrety nie trafiają do paczki ani propsów klienta, a każda wrażliwa operacja sprawdza uprawnienia po stronie serwera.
  • Filtry i paginacja odtwarzają się z URL, przycisk Wstecz zachowuje się zgodnie z projektem, a warianty adresów mają ustalone reguły indeksowania.
  • Formularze przewidziane do działania bez JavaScriptu przetestowano z wyłączonym JS; pozostałe mają sprawdzone oczekiwanie, błędy i ponowienie.
  • Po mutacji użytkownik widzi aktualne dane, a test z dwiema sesjami nie ujawnia danych innego użytkownika przez cache.
  • Zmierzono początkowy JavaScript, zasoby i główne interakcje dla kluczowych tras; wyniki porównano z przyjętym budżetem wydajności.
  • Główna ścieżka przeszła E2E, kontrolę obsługi klawiaturą i sprawdzenie na wąskim ekranie.
Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.
Next.js

Często zadawane pytania

Czy Server Components zastępują React Query?

Nie w każdym przypadku. Server Components często wystarczają do przygotowania widoku z danymi po stronie serwera. React Query przydaje się, gdy przeglądarka ma zarządzać dalszym odświeżaniem, synchronizacją i cache danych. Oba rozwiązania mogą działać razem, jeśli jasno określisz ich odpowiedzialności.

Czy trzeba migrować z Pages Routera do App Routera?

Nie tylko po to, aby korzystać z nowszej architektury. Migracja powinna przynieść konkretną korzyść, np. wykorzystanie RSC lub zmianę sposobu renderowania wybranych tras. Można przeprowadzać ją etapami. Utrzymanie Pages Routera nie zwalnia natomiast z aktualizacji bezpieczeństwa samego frameworka.

Czy React Compiler zwalnia z useMemo i useCallback?

Zmniejsza potrzebę ręcznej memoizacji w kodzie, który rzeczywiście kompiluje. Nie oznacza to zakazu używania tych hooków ani zalecenia usunięcia wszystkich istniejących wywołań. Najpierw sprawdź konfigurację kompilatora i wyniki pomiarów; ręczną kontrolę zachowaj tam, gdzie ma uzasadnienie.

Gdzie trzymać token w aplikacji React?

To zależy od architektury. W Next.js z własnym serwerem warto rozważyć trzymanie tokenów zewnętrznych usług po stronie serwera i sesję przeglądarki opartą na bezpiecznie skonfigurowanym cookie HttpOnly. W samodzielnej SPA zastosuj przepływ zalecany przez dostawcę logowania. localStorage nie jest uniwersalnym, bezpiecznym wyborem dla długowiecznych tokenów.

Ile kodu powinno być po stronie klienta?

Nie ma prawidłowego procentu ani jednego limitu kilobajtów dla wszystkich produktów. Przeglądarka potrzebuje kodu obsługującego interakcje i funkcje zależne od jej środowiska. Oceniaj początkowy transfer, czas wykonania i responsywność na urządzeniach odbiorców. Rozbudowany edytor ma inne potrzeby niż strona ofertowa.

O autorze

Maciej Sala

Maciej Sala — konsultant technologiczny produktów cyfrowych i web developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną
LH.pl – Hosting Mango

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
React Server Components i Server Actions: SEO i wydajność

App Router pozwala przesunąć pobieranie danych i renderowanie treści na serwer, zostawiając w przeglądarce małe obszary interakcji. Taki podział może ograniczyć JavaScript i ułatwić dostarczenie treści w HTML, ale nie gwarantuje dobrych Core Web Vitals ani indeksacji. Wynik zależy też od czasu odpowiedzi serwera, cache, obrazów, fontów, metadanych i bezpieczeństwa mutacji.

Maciej Sala

Maciej Sala

Founder StriveLab

Next.js jako backend: architektura aplikacji full-stack

Next.js może obsługiwać interfejs, logikę biznesową i dostęp do bazy w jednym projekcie. Ten przewodnik pokazuje, jak połączyć komponenty serwerowe, Server Actions i Route Handlers z autoryzacją, cache i zadaniami w tle. Na przykładzie panelu zamówień przejdziemy od odczytu danych do zapisu i utrzymania na produkcji, wskazując momenty, w których warto wydzielić osobną usługę.

Maciej Sala

Maciej Sala

Founder StriveLab

App Router czy Pages Router w Next.js: co wybrać?

Next.js 13 wprowadził App Router , czyli nowy sposób budowania aplikacji oparty na React Server Components . Ale Pages Router nigdzie nie zniknął. Dwa routery, dwa podejścia, jedna decyzja do podjęcia na starcie projektu.

Maciej Sala

Maciej Sala

Founder StriveLab

LH.pl – Cloud Server 1C4G