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 ładowaniauseFormState
dla obsługi błędów walidacjiuseOptimistic
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()
lubheaders()
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
Dodaj komentarz
Musisz się zalogować, aby móc dodać komentarz.