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?

Choć frontend odpowiada za całościowe doświadczenie użytkownika, nie cały jego kod musi być wykonywany bezpośrednio w przeglądarce. Serwer doskonale sprawdza się przy przygotowaniu listy produktów, opisów usług czy całej struktury witryny. Samej przeglądarce warto pozostawić jedynie płynną obsługę zdarzeń i stanu lokalnego, czyli otwieranie paneli, interakcje drag and drop czy bieżącą wartość pola wyszukiwania.

W App Routerze strony i layouty stanowią domyślnie Server Components i właśnie to określenie definiuje miejsce wykonania kodu, a nie sztywny wymóg renderowania przy każdym żądaniu. Komponent serwerowy może być renderowany zarówno w fazie prerenderowania, jak i dynamicznie podczas obsługi żądania — w zależności od strategii cache'owania oraz samej natury pobieranych danych.

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

Dyrektywa 'use client' wyznacza ścisłą granicę w drzewie zależności: moduł oznaczony tą dyrektywą oraz wszystkie importowane przez niego komponenty i biblioteki wchodzą do paczki klienckiej, nawet jeśli same jej nie posiadają. Z tego powodu umieszczenie 'use client' na poziomie dużego kontenera ma znacznie poważniejsze konsekwencje dla rozmiaru bundle'a niż wydzielenie małego, interaktywnego przycisku. Co istotne, wizualne zagnieżdżenie w komponencie klienckim nie wymusza wykonania kodu po stronie przeglądarki — komponent serwerowy może przekazać wstępnie wyrenderowaną zawartość za pomocą właściwości children. O ostatecznym środowisku wykonania decyduje bowiem architektura importów i sposób kompozycji, a nie sama struktura drzewa JSX.

Diagram
Opis pozostaje serwerowy, choć panel wyświetla go wewnątrz siebie. Panel importuje przycisk, a gotowy opis otrzymuje przez children. Komponenty klienckie mogą też uczestniczyć w początkowym SSR.

Komponent kliencki nie oznacza braku renderowania po stronie serwera (SSR). Jego bazowy kod HTML nadal może powstać na serwerze, podczas gdy późniejszy proces hydratacji odpowiada jedynie za dołączenie obsługi zdarzeń i interakcji. Wymaga to bezwzględnej spójności pomiędzy pierwszym renderem serwerowym a klienckim. Przyczyny niezgodności hydratacji często wynikają z użycia dynamicznego czasu, losowych wartości czy odczytu ustawień przeglądarki bezpośrednio podczas renderowania, ale mogą mieć też prozaiczne podłoże, jak niepoprawne zagnieżdżenie znaczników HTML — nie stanowią więc automatycznie dowodu błędnej architektury całej aplikacji.

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.

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. Trzeba przy tym ustalić, które zmiany dopisują wpis do historii, bo inaczej przycisk Wstecz zacznie odtwarzać każdą wpisaną literę.

Parametry URL nie wykluczają indeksowania. To, które kombinacje filtrów mają być osobnymi stronami w wyszukiwarce, a które tylko wariantami użytkowymi, jest decyzją SEO. Rozwija ją artykuł o parametrach w URL i duplikacji treści.

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ć modyfikację danych wokół samej operacji oraz jej wyniku. W Next.js Server Actions umożliwiają bezpieczne wykonanie zapisu bezpośrednio po stronie serwera, podczas gdy hook useActionState ułatwia zarządzanie stanem oczekiwania i prezentacją rezultatu, a useOptimistic pozwala natychmiast wyświetlić przewidywany efekt interakcji. Solidnym punktem wyjścia dla prostego formularza pozostają natywne pola HTML, akcja serwerowa oraz walidacja po stronie serwera. Walidacja w przeglądarce skraca co prawda czas reakcji na błędy użytkownika, jednak w żadnym stopniu nie zastępuje weryfikacji serwerowej — każda operacja zapisu musi bezwzględnie ponownie sprawdzić tożsamość użytkownika oraz jego uprawnienia do modyfikowanego zasobu.

Po mutacji należy odpowiedzieć na jeszcze jedno pytanie: skąd reszta interfejsu dowie się o wprowadzonej zmianie? Jeśli widok korzysta jednocześnie z pamięci podręcznej serwera i klienta, odświeżenie tylko jednej z tych warstw może prowadzić do niespójności danych. Sposób unieważniania (ang. invalidation) i rewalidacji cache'u musi być zatem integralną częścią projektu samej mutacji, a nie kwestią odkładaną na etap dopracowywania UI. Formularz wykorzystujący Server Action przekazaną bezpośrednio do atrybutu action i renderowany w komponencie serwerowym potrafi obsłużyć wysyłkę danych nawet bez załadowanego kodu JavaScript. Nie oznacza to jednak, że każdy formularz z akcją serwerową zachowuje tę właściwość — własne zdarzenia interfejsu, pola renderowane dynamicznie po interakcji czy kliencka nawigacja między krokami natychmiast tworzą bezwzględną zależność od środowiska przeglądarki.

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

W architekturze RSC design system jest także kontraktem dotyczącym 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 wydajności rozpoczyna się od precyzyjnej diagnozy, na co dokładnie czeka użytkownik: powolny serwer, zbyt duży plik graficzny czy przeciążony główny wątek przeglądarki wymagają zupełnie innych działań naprawczych. W samej warstwie frontendu sprawdza się taka kolejność:

  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ę.

Wykorzystanie React Server Components nie jest równoznaczne z całkowitym wyeliminowaniem transferu danych. Do przeglądarki klienta wciąż przesyłana jest struktura HTML, strumień danych protokołu RSC oraz kod źródłowy komponentów klienckich. W związku z powyższym przekazanie tysięcy rekordów do niewielkiego widgetu potrafi w praktyce w całości zniwelować korzyści wydajnościowe wynikające 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, ale tylko w kodzie, który kompilator faktycznie obejmuje. Sama obecność Reacta 19 nie dowodzi, że kompilator działa w projekcie, a kosztu sieci ani nadmiarowych zależności i tak nie usunie. 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, bo wynik laboratoryjny pomaga diagnozować, lecz nie zastępuje doświadczeń użytkowników. Co mierzą LCP, INP i CLS oraz jak je poprawiać, opisuje przewodnik po Core Web Vitals.

Bezpieczeństwo po stronie klienta

Typ w TypeScripcie 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.

Kod wykonywany w przeglądarce oraz przesyłane do niej dane są w pełni dostępne dla użytkownika, dlatego tajne klucze API muszą bezwzględnie pozostać po stronie serwera. Zasada ta dotyczy również samej struktury danych — nawet komponent serwerowy nie ochroni sekretu, jeśli przekaże go bezpośrednio w propsach do komponentu klienckiego. Do warstwy interfejsu należy przekazywać wyłącznie te pola, które są niezbędne do jego prawidłowego 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 uniemożliwia odczyt cookie przez JavaScript strony, 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.

Jak to testować

Wyraźna granica serwer–klient ułatwia także rozdzielenie strategii testowej. Czysta funkcja ustalająca dostępne warianty produktu nie wymaga środowiska przeglądarki. Z kolei pełna ścieżka, czyli „zmień wariant, zapisz, odśwież stronę”, to już test integracyjny, sprawdzający współpracę kilku warstw aplikacji.

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 dla asynchronicznych komponentów serwerowych w popularnych bibliotekach do testów jednostkowych pozostaje ograniczone, dlatego oficjalna dokumentacja Next.js rekomenduje weryfikację tego obszaru na poziomie testów E2E. Nie oznacza to jednak konieczności rezygnacji z testowania samej logiki biznesowej — wystarczy odseparować ją od warstwy prezentacji i weryfikować jako niezależne, czyste funkcje serwerowe.

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 Next.js nadaje się do aplikacji bez własnego serwera?

Tak, jeśli wystarczy statyczny eksport (output: 'export'). Server Components renderują się wtedy podczas builda, a Client Components działają normalnie w przeglądarce. Tracisz jednak wszystko, co wymaga serwera w trakcie żądania: Server Actions, odczyt cookies i nagłówków, dynamiczne trasy bez generateStaticParams, ISR oraz domyślną optymalizację obrazów.

Kiedy zamiast Next.js lepiej wybrać Astro albo React z Vite?

Astro sprawdza się przy serwisach opartych na treści, w których interaktywne są tylko wybrane fragmenty. React z Vite jako SPA bywa prostszy dla aplikacji za logowaniem, która nie potrzebuje widoczności w wyszukiwarce ani renderowania na serwerze. Next.js ma przewagę, gdy łączysz treść indeksowaną w Google z rozbudowaną interakcją. Szerzej porównuję to w artykule o przewadze Next.js nad zwykłym Reactem.

Co zrobić z biblioteką UI, która działa tylko w przeglądarce?

Jeśli komponent z zewnętrznego pakietu korzysta ze stanu lub efektów, a nie ma dyrektywy 'use client', zaimportuj go we własnym pliku oznaczonym tą dyrektywą i eksportuj dalej. Takiego wrappera możesz używać w Server Components. Komponenty zależne od window przy pierwszym renderze ładuj dynamicznie z wyłączonym SSR tylko wtedy, gdy nie da się tego rozwiązać inaczej.

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