29 błędów Next.js, które popełnia każdy początkujący deweloper #EN310

Notatki z prezentacji Wesley’a o najczęstszych pułapkach w Next.js App Router

W skrócie

  • Niewłaściwe umieszczanie dyrektywy 'use client’ prowadzi do niepotrzebnego zwiększenia rozmiaru paczki klienckiej
  • Server actions w większości przypadków zastępują tradycyjne API routes przy mutacjach danych
  • Client components wykonują się zarówno na serwerze (wstępne renderowanie) jak i w przeglądarce, co wymaga szczególnej uwagi przy używaniu browser APIs
  • Funkcje cookies(), headers() oraz search params automatycznie zmieniają rendering z static na dynamic
  • Server actions wymagają takiej samej walidacji i zabezpieczeń jak publiczne API endpoints
  • Suspense boundaries należy stosować granularnie – blokowanie całej strony dla jednego komponentu pogarsza doświadczenie użytkownika
  • Environment variables są chronione przez Next.js – tylko zmienne z prefiksem NEXT_PUBLIC_ trafiają do przeglądarki

Wprowadzenie

Wesley przedstawia 29 najczęstszych błędów w Next.js, które popełniają zarówno początkujący jak i doświadczeni deweloperzy. Prezentacja opiera się na prostym przykładzie aplikacji sklepowej, który ilustruje kluczowe różnice między server i client components.

Autor uspokaja: każdy deweloper Next.js popełnił większość z tych błędów podczas nauki nowych paradygmatów. Framework wprowadza rewolucyjne koncepcje jak server components, server actions, suspense i streaming, które wymagają zmiany sposobu myślenia o architekturze aplikacji.

Server vs client components – fundamentalne pułapki

Błąd 1: dyrektywa 'use client’ zbyt wysoko w hierarchii

Wesley uznaje to za najkosztowniejszy błąd. Dodanie dyrektywy 'use client’ do komponentu strony powoduje automatyczne przekształcenie wszystkich importowanych komponentów w client components.

Konsekwencje są poważne:

  • Utrata możliwości bezpośredniego fetchowania danych na serwerze
  • Znaczne zwiększenie rozmiaru paczki klienckiej
  • Utrata wszystkich korzyści płynących z server components
  • Wyższe koszty obliczeniowe i dłuższy czas ładowania

Według Wesley’a, dyrektywa powinna znajdować się na brzegach drzewa komponentów – typowo w przyciskach, polach formularzy i innych małych elementach wymagających interaktywności.

Błąd 2: brak refaktoryzacji dla client components

Zamiast dodawać 'use client’ do całego komponentu strony, należy wyodrębnić interaktywny element do osobnego komponentu. Dzięki temu większość aplikacji pozostaje jako server components, co przynosi korzyści wydajnościowe.

Błąd 3: mylne założenia o charakterze komponentów

Komponent może być client component nawet bez widocznej dyrektywy 'use client’ w swoim pliku. Wystarczy że zostanie zaimportowany do pliku zawierającego tę dyrektywę. Wesley zaleca jednak jawne dodawanie 'use client’ do komponentów, które zawsze muszą działać w przeglądarce – zwiększa to czytelność kodu i przewidywalność zachowania.

Błąd 4: nieznajomość wzorca children dla przeplatania komponentów

Server component może pozostać server component mimo renderowania wewnątrz client component. Kluczem jest wzorzec children – gdy client component przyjmuje server component jako prop children i po prostu go przekazuje dalej, React zachowuje server component na serwerze.

Ta mechanika umożliwia przeplatanie komponentów różnego typu, co bywa mylące dla programistów przyzwyczajonych do tradycyjnego Reacta.

Błąd 5: próby state management na serwerze

Context API, Zustand i inne rozwiązania do zarządzania stanem działają wyłącznie po stronie klienta. Wesley wyjaśnia to logiką architektury: serwer pracuje w cyklu request-response i po przetworzeniu żądania nie zachowuje żadnego stanu. Z kolei przeglądarka utrzymuje stan przez cały czas interakcji użytkownika z aplikacją.

Błędy 6-11: hydracja i browser APIs

Client components działają w dwóch miejscach: na serwerze podczas wstępnego renderowania oraz w przeglądarce podczas hydracji. Próba użycia window, localStorage czy innych browser APIs wymaga ostrożności.

Wesley pokazuje trzy rozwiązania tego problemu:

  • Sprawdzenie typeof window !== 'undefined' przed użyciem browser API
  • Wykorzystanie useEffect, który nie wykonuje się podczas renderowania serwerowego
  • Dynamic import z opcją ssr: false dla komponentów wymagających wyłącznie środowiska przeglądarki

Błędy hydracji powstają gdy HTML wygenerowany na serwerze różni się od tego w przeglądarce. Najczęstsze przyczyny to nieprawidłowa struktura HTML lub różnice w danych generowanych w czasie wykonania (np. new Date() da inne wyniki na serwerze i kliencie).

Błąd 11: nieprawidłowe radzenie sobie z komponentami zewnętrznymi

Komponenty z bibliotek NPM często używają React hooks lub event handlers bez dyrektywy 'use client’. Wesley pokazuje rozwiązanie rekomendowane przez dokumentację Next.js – stworzenie komponentu opakowującego z 'use client’, który re-exportuje zewnętrzny komponent.

Jeśli komponent używa browser APIs, sam 'use client’ może nie wystarczyć. Wtedy konieczny jest dynamic import z ssr: false aby komponent wykonywał się wyłącznie w przeglądarce.

Data fetching i server actions

Błąd 12: niepotrzebne route handlers do pobierania danych

Wesley podkreśla fundamentalną zmianę w Next.js: skoro server component działa na serwerze, wszystko co można zrobić w route handler, można zrobić bezpośrednio w server component. Eliminuje to niepotrzebną warstwę abstrakcji i upraszcza kod.

Błąd 13: obawy o duplikowanie wywołań fetch

Next.js automatycznie cache’uje wywołania fetch na dwa sposoby. Po pierwsze, React deduplikuje identyczne wywołania w tym samym render pass – jeśli dwa komponenty fetchują te same dane, faktycznie wykona się tylko jedno zapytanie.

Wesley wyjaśnia jeszcze potężniejszy mechanizm: Next.js data cache. Ten cache przechowuje wyniki wywołań fetch i jest tak silny, że przetrwa nawet deployment aplikacji.

Dla ORMs jak Prisma sytuacja jest inna. Te biblioteki mogą mieć własne mechanizmy cache’owania, dlatego Next.js nie robi tego automatycznie. Wesley pokazuje dwa rozwiązania:

  • Funkcja cache z React deduplikuje dla pojedynczego request (tymczasowo)
  • Funkcja unstable_cache z Next.js tworzy persistent cache analogiczny do fetch (trwa przez deployments)

Zgodnie z zaleceniem Wesley’a, można bezpiecznie fetchować dane bezpośrednio w miejscu gdzie są potrzebne.

Błąd 14: waterfalls przy pobieraniu danych

Sekwencyjne pobieranie niezależnych danych to marnotrawstwo czasu. Wesley ilustruje to przykładem: trzy wywołania trwające po 2 sekundy wykonane sekwencyjnie zajmą łącznie 6 sekund. Jednak wykonane równolegle – zaledwie 2 sekundy.

Rozwiązanie to Promise.all() lub Promise.allSettled() dla większej odporności na błędy pojedynczych zapytań.

Błędy 15-19: server actions i bezpieczeństwo

Wesley uznaje server actions za największą innowację w Next.js. Zastępują tradycyjne API routes prostymi funkcjami JavaScript, eliminując potrzebę tworzenia całych endpointów.

Kluczowe zasady bezpieczeństwa według Wesley’a:

  • Każda server action to publiczny POST endpoint dostępny dla wszystkich
  • Wymagana walidacja danych wejściowych (np. biblioteka Zod)
  • Sprawdzanie autentyfikacji i autoryzacji użytkownika
  • Używanie revalidatePath() do odświeżania cache po mutacji danych
  • Traktowanie server actions jak tradycyjnych API endpoints pod względem zabezpieczeń

Wesley przestrzega: inne osoby mogą technicznie wysyłać requesty do tych endpointów, dlatego każda server action wymaga takiej samej walidacji jak publiczne API.

Błąd 17: przekonanie że server actions działają tylko w server components

Wesley wyraźnie podkreśla możliwość używania server actions również w client components. Można je wywoływać bezpośrednio z event handlerów jak onClick. Next.js automatycznie zadba o przesłanie danych z przeglądarki na serwer.

Server actions współpracują z trzema potężnymi React hooks:

  • useFormStatus dla stanu pending i wskaźników ładowania
  • useFormState dla obsługi błędów walidacji
  • useOptimistic dla optimistic UI updates

Dodatkowa zaleta: formularz z server action działa nawet bez włączonego JavaScript. To przykład progressive enhancement (stopniowego wzbogacania funkcjonalności) dostępnego out of the box.

Organizacja server actions w projekcie

Wesley zaleca separację server actions do dedykowanego folderu. W jego podejściu folder actions zawiera plik index.ts ze wszystkimi server actions zebranymi w jednym miejscu.

Możliwe jest również definiowanie server actions bezpośrednio w komponencie, ale Wesley uważa to za mniej czytelne rozwiązanie. Podobnie dla server-only utilities zaleca osobny plik server-utils.ts aby wyraźnie oznaczyć funkcje przeznaczone wyłącznie dla serwera.

Routing i search params

Błąd 20: niezrozumienie dynamic routes vs search params

Dynamic routes ([id]) to części URL w nawiasach kwadratowych reprezentujące zmienne parametry. Z kolei search params to query string występujący po znaku zapytania. Oba mechanizmy przekazują różne props do komponentu strony.

Błąd 21: nieprawidłowa praca z search params

Odczytywanie search params przez props wymaga network request do serwera. Wesley pokazuje alternatywę: hook useSearchParams w client component, który działa lokalnie bez dodatkowych zapytań sieciowych.

Wybór podejścia zależy od wymagań – server-side dla korzyści SEO i cache’owania, client-side dla szybkiej interaktywności bez opóźnień sieciowych.

Błąd 22: zapominanie o lokalnym charakterze developmentu

Wesley podkreśla powszechny problem: podczas developmentu wszystko działa szybko bo serwer jest na lokalnym komputerze. Jednak w produkcji każde zapytanie do serwera może trwać znacznie dłużej. Brak stanów ładowania to jeden z najczęstszych błędów wynikających z testowania wyłącznie w środowisku lokalnym.

Suspense i loading states

Błąd 23: nieoptymalna granularność suspense

Wesley zaleca granularne podejście do suspense boundaries. Blokowanie całej strony dla jednego komponentu to zły pomysł – użytkownik czeka na wszystko zamiast zobaczyć część strony natychmiast.

Należy rozważyć doświadczenie użytkownika: lepiej pokazać część strony szybko (nagłówek, nawigację, inne komponenty) niż całą po długim czasie oczekiwania.

Błąd 24: suspense w niewłaściwym miejscu

Wesley ostrzega przed powszechnym błędem polegającym na umieszczaniu suspense w niewłaściwym miejscu. Komponent Suspense MUSI być umieszczony wyżej niż miejsce gdzie faktycznie awaitujesz dane.

Nie można umieścić suspense wewnątrz komponentu który robi await – to nie zadziała. Suspense musi owijać komponent z zewnątrz.

Błąd 25: brak key prop dla suspense

Prop key umożliwia retriggowanie suspense przy zmianie parametrów URL. React nie wie automatycznie że suspense powinien się retriggować gdy zmienia się np. ID produktu w search params.

Bez odpowiedniego klucza suspense uruchomi się tylko raz – przy pierwszym załadowaniu strony. Kolejne zmiany parametrów nie wyzwolą ponownie loading state.

Static vs dynamic rendering

Błąd 26: przypadkowe przechodzenie na dynamic rendering

Wesley uznaje static rendering za najważniejszą optymalizację w Next.js. Ten tryb generuje HTML na etapie build, serwuje go z CDN i nie wymaga ponownego renderowania przy każdym żądaniu.

Przypadkowe przejście na dynamic rendering następuje przez:

  • Używanie funkcji cookies() lub headers() w komponentach
  • Używanie search params props w komponencie strony
  • Importowanie bibliotek uwierzytelniania w layoutach globalnych
  • Umieszczanie komponentów z dynamic funkcjami w shared components
  • Nieświadome używanie third-party bibliotek korzystających z dynamic APIs

Dynamic rendering oznacza renderowanie przy każdym żądaniu – wolniejsze i droższe obliczeniowo.

Błąd 27: nieostrożne używanie funkcji dynamic

Nawet gdy programista nie używa bezpośrednio cookies(), biblioteka uwierzytelniania może to robić pod spodem. Dlatego umieszczenie takiego komponentu w layout wpływa na całą aplikację, zmieniając wszystkie route’y na dynamic.

Wesley zaleca regularne sprawdzanie npm run build aby monitorować które routes są static/dynamic i wyłapywać przypadkowe zmiany trybu renderowania.

Bezpieczeństwo i environment variables

Błędy 27-29: zarządzanie sekretami

Environment variables w Next.js są domyślnie chronione – nie trafiają do bundle klienta. Udostępnienie zmiennej klientowi wymaga jawnego dodania prefiksu NEXT_PUBLIC_.

Wesley zaleca pakiet server-only dla funkcji i komponentów, które nie powinny nigdy trafić do klienta. Zapewnia to dodatkową warstwę ochrony przed przypadkowym wyciekiem wrażliwych danych.

Ostatni błąd: redirect w try-catch

Funkcja redirect() działa przez rzucanie błędu. W bloku try-catch zostanie złapana zamiast wykonana, co uniemożliwi przekierowanie użytkownika. Wesley radzi używać redirect poza try-catch lub być świadomym tego mechanizmu przy używaniu bibliotek uwierzytelniania, które mogą wewnętrznie wykorzystywać redirect.

Praktyczne wnioski

Prezentacja Wesley’a pokazuje jak Next.js abstrakcjonuje granicę między klientem a serwerem, ale wymaga nowego sposobu myślenia o architekturze aplikacji. Kluczem jest zrozumienie kiedy i gdzie kod wykonuje się oraz jakie ma to konsekwencje dla wydajności i bezpieczeństwa.

Wesley podsumowuje szczerze: prawdopodobnie część materiału może wydawać się myląca, ale to całkowicie normalne. Next.js wprowadza wiele nowych paradygmatów i przyzwyczajenie się do nich wymaga czasu.

Największe korzyści przynosi przemyślane umieszczanie granic client/server components, wykorzystanie server actions zamiast tradycyjnych API routes, świadome zarządzanie static/dynamic rendering i właściwe zabezpieczanie server actions jak publicznych endpoints.

Regularne uruchamianie npm run build pozwala monitorować co dzieje się z renderingiem aplikacji. To proste narzędzie diagnostyczne może wyłapać wiele problemów zanim trafią do produkcji.

Kluczowy insight

Fetchuj tam gdzie potrzebujesz

Standardowo myślimy: Trzeba fetchować dane na najwyższym poziomie w hierarchii komponentów i przekazywać je props w dół, żeby uniknąć duplikacji requestów i zachować wydajność.

W praktyce okazuje się, że: W Next.js można bezpiecznie fetchować te same dane w wielu różnych komponentach. React deduplikuje identyczne wywołania w tym samym render pass, a Next.js cache’uje wyniki w data cache który przetrwa nawet deployment.

Dlaczego to jest istotne: Eliminacja prop drillingu przez wiele warstw komponentów. Każdy komponent samodzielnie deklaruje jakich danych potrzebuje. Kod staje się czystszy, łatwiejszy w refaktoryzacji i bardziej lokalny w swojej logice.

Test na jutro: Następnym razem gdy budujesz komponent potrzebujący danych które już są fetchowane wyżej w drzewie, zamiast przekazywać je przez trzy warstwy props, po prostu zrób fetch bezpośrednio w komponencie i sprawdź w Network tab że request wykonał się tylko raz. Dopiero wtedy uwierzysz.

Checklist przed wdrożeniem

✅ Server/Client Components

  • [ ] Sprawdź czy 'use client’ jest na najniższym możliwym poziomie w drzewie
  • [ ] Zweryfikuj czy komponenty używające hooks/events są client components
  • [ ] Upewnij się że server components nie używają browser APIs bezpośrednio
  • [ ] Zastosuj wzorzec children dla server components w client components
  • [ ] Komponenty zewnętrzne bez 'use client’ opakowane we wrapper component

✅ Security & Server Actions

  • [ ] Wszystkie server actions mają walidację danych wejściowych (np. Zod)
  • [ ] Dodano sprawdzanie autentyfikacji i autoryzacji w server actions
  • [ ] Sekrety przechowywane jako environment variables (bez NEXT_PUBLIC_)
  • [ ] Używasz revalidatePath() po mutacjach danych
  • [ ] Server-only utilities używają pakietu 'server-only’
  • [ ] Rozważono użycie useFormStatus/useFormState dla lepszego UX

✅ Performance & Rendering

  • [ ] Uruchom npm run build i sprawdź które routes są static/dynamic
  • [ ] Unikaj używania cookies()/headers() w shared layouts
  • [ ] Search params używane przez hook zamiast props gdzie to możliwe
  • [ ] Loading states są granularne (suspense na poziomie komponentów, nie stron)
  • [ ] Suspense umieszczony WYŻEJ niż miejsce await (nie wewnątrz komponentu)
  • [ ] Klucze key dodane do suspense przy zmieniających się parametrach

✅ Data Fetching

  • [ ] Fetchowanie danych bezpośrednio w server components zamiast API routes
  • [ ] Równoległe pobieranie niezależnych danych (Promise.all/allSettled)
  • [ ] Cache revalidation ustawiony tam gdzie potrzebny

Ten wpis jest częścią mojej kolekcji notatek z ciekawych podcastów, webinarów i innych treści, które uważam za wartościowe i do których sam chcę wracać. Jeśli chcesz sprawdzić oryginalne źródło, znajdziesz je tutaj: All 29 Next.js Mistakes Beginners Make


Opublikowano

Komentarze

Dodaj komentarz