Opanuj narzędzia do debugowania Next.js. Zobacz, jak sprawnie rozwiązywać błędy hydratacji, kontrolować Server Components i czyścić zablokowany cache.
Maciej Sala
Founder StriveLab
5 min czytaniaOpublikowano 11 kwietnia 2026 (Aktualizacja 14 lipca 2026)
Dlaczego debugowanie Next.js jest trudniejsze niż klasycznego Reacta?
Zanim wejdziemy w konkretne problemy, jedna mapa, która oszczędza najwięcej czasu — od objawu do właściwej warstwy i narzędzia:
Diagram
Ścieżka diagnozy: najpierw ustal warstwę, w której występuje objaw, dopiero potem sięgaj po narzędzie.
Problem 1: hydration mismatch w Next.js
Najczęstszy błąd w Next.js: „Hydration failed because the initial UI does not match what was rendered on the server." pojawia się, gdy serwer i klient renderują inny początkowy wynik.
Typowe przyczyny hydration mismatch
Code
// 1. Date/Time — serwer i klient mają różne timezone/czas// ŹLE;<p>Wygenerowano: {new Date().toLocaleString()}</p>// Serwer: "11.04.2026, 10:00:00" → Klient: "11.04.2026, 10:00:01" → MISMATCH// DOBRZE — renderuj datę tylko na kliencie;('use client')function TimeStamp() { const [time, setTime] = useState<string>('') useEffect(() => setTime(new Date().toLocaleString()), []) return <p>{time}</p>}
Code
// 2. window/document — nie istnieją na serwerze// ŹLE;<p>Szerokość: {window.innerWidth}px</p> // window is not defined on server// DOBRZE;('use client')function ScreenWidth() { const [width, setWidth] = useState(0) useEffect(() => setWidth(window.innerWidth), []) return <p>Szerokość: {width}px</p>}
Code
// 3. Math.random() — różny wynik na serwerze i kliencie// ŹLE<div style={{ order: Math.random() }}>...</div>// DOBRZE — użyj deterministycznego źródła<div style={{ order: hashString(item.id) }}>...</div>
Code
// 4. Rozszerzenia przeglądarki modyfikują DOM// Grammarly, LastPass, Dark Reader dodają elementy do <body>// Rozwiązanie: suppressHydrationWarning na <body><body suppressHydrationWarning>
Jak debugować hydration errors w Next.js?
Współczesny Next.js pokazuje diff w dev overlay — porównanie HTML serwera vs klienta. Jeśli diff jest nieczytelny:
Code
// Tymczasowo wyłącz SSR na podejrzanym komponencieimport dynamic from 'next/dynamic'const SuspectedComponent = dynamic(() => import('./suspected'), { ssr: false, // Renderuj tylko na kliencie — jeśli błąd zniknie, problem jest w SSR})
Problem 2: cache w Next.js i dane, które się nie odświeżają
„Zmieniłem dane w bazie, ale strona pokazuje stare."
Diagnostyka cache w App Routerze
Code
// 1. Sprawdź, czy strona jest statyczna czy dynamiczna// Build output pokaże:// ○ = Static (cachowana w build)// ƒ = Dynamic (renderowana per request)// ● = SSG z ISR// 2. Sprawdź fetch cacheconst data = await fetch(url, { cache: 'no-store', // Wymusza świeże dane // cache: 'force-cache', // Jawnie włącza cache — od Next.js 15 fetch domyślnie NIE jest cachowany})// 3. Sprawdź revalidateexport const revalidate = 0 // Wymusza dynamiczne renderowanie// export const revalidate = 3600; // Odśwież co godzinę
Zanim zaczniesz cokolwiek „naprawiać", upewnij się, że patrzysz na właściwe środowisko: dev server renderuje strony na świeżo przy każdym żądaniu, żeby zmiany w kodzie były widoczne natychmiast. Statycznych stron, ISR ani pełnego cache routera w trybie dev po prostu nie zobaczysz — zachowanie cache testuj po next build && next start. Odwrotna pomyłka też się zdarza: „u mnie działa" w dev, a na produkcji strona okazuje się statyczna i pokazuje dane z momentu builda.
Czyszczenie cache Next.js w development
Code
# Wyczyść cache Next.jsrm -rf .next# Restart dev serveranpm run dev
Debugowanie revalidation w Next.js
Code
// Dodaj logi do revalidationimport { revalidatePath, revalidateTag } from 'next/cache'export async function updatePost(id: string, data: PostData) { await db.post.update({ where: { id }, data }) console.log(`[REVALIDATE] Path: /blog/${id}`) revalidatePath(`/blog/${id}`) console.log(`[REVALIDATE] Tag: post-${id}`) revalidateTag(`post-${id}`)}
Eksporty dynamic i revalidate
Code
// Wymuś dynamiczne renderowanie (brak cache)export const dynamic = 'force-dynamic'// Wymuś statyczne renderowanie (error jeśli użyjesz cookies/headers)export const dynamic = 'force-static'// ISR — revalidate co N sekundexport const revalidate = 3600// Zero cache (każdy request = nowy render)export const revalidate = 0
Problem 3: debugowanie Server Components i console.log
To pytanie zadaje sobie każdy, kto pierwszy raz dotyka App Routera. wykonuje się na serwerze, więc jego logi lądują w terminalu, nie w DevTools.
Code
// Server Component — log pojawia się w TERMINALU, nie w przeglądarceexport default async function ProductPage() { const product = await getProduct('123') console.log('Product data:', product) // ← Terminal (serwer) return <div>{product.name}</div>}
Code
// Client Component — log pojawia się w PRZEGLĄDARCE (DevTools Console)'use client'export function AddToCartButton() { console.log('Button rendered') // ← Przeglądarka return <button onClick={() => console.log('Clicked!')}>Dodaj</button>}
Zwróć uwagę, że akcja zwraca obiekt błędu, a nie go rzuca. To nie jest kwestia stylu: wyjątek rzucony z akcji nie trafia do state — leci wyżej, do najbliższego error boundary, a na produkcji jego treść jest dodatkowo maskowana przez Next.js. Stan błędu, który chcesz pokazać przy formularzu, musi być wartością zwracaną — dlatego Server Action powinna zwracać { success, error } zamiast rzucać dla spodziewanych niepowodzeń.
Problem 5: debugowanie middleware i proxy w Next.js
Warstwa pośrednicząca między żądaniem a trasą bywa trudna do podejrzenia, bo działa poza zwykłym cyklem komponentów. W Next.js 16 plik middleware.ts został przemianowany na proxy.ts (a funkcja na proxy) i działa na runtime Node; w starszych wersjach to middleware.ts na Edge Runtime z ograniczonym logowaniem. Niezależnie od wersji pomaga structured logging:
React DevTools w Chrome/Firefox pokazuje drzewo komponentów, propsy, state i konteksty. Profiler mierzy czas renderowania — szukaj komponentów, które renderują się zbyt często.
Uwaga: React DevTools nie pokazuje Server Components (bo nie istnieją w przeglądarce). Widzisz tylko Client Components.
Next.js Dev Overlay
Automatycznie wyświetla się w dev mode przy błędach: hydration mismatch, runtime errors, build errors. Kliknij na błąd — przeniesie Cię do linii kodu w edytorze.
Network tab: sprawdź, co fetchuje Next.js
Chrome DevTools → Network → filtruj po Fetch/XHR. Widzisz requesty z Client Components. Server Components fetchują na serwerze — ich requesty nie pojawiają się w Network tab przeglądarki.
Żeby zobaczyć server-side fetch, zacznij od wbudowanej opcji — Next.js potrafi logować wszystkie fetche z Server Components w terminalu dev servera, razem ze statusem cache (HIT/MISS/SKIP):
Na produkcji console.log nie wystarcza, ponieważ potrzebujesz prawdziwego monitoringu. Sentry zbierze błędy i opcjonalnie nagrania sesji, Vercel Analytics albo własny reporter Core Web Vitals pokaże wydajność u realnych użytkowników, a structured logging (JSON do stdout zbierany przez agregator) pozwoli przeszukiwać zdarzenia. Klucz to logować w formacie nadającym się do filtrowania i alarmowania, a nie tylko wypisywać tekst.
Czy mogę użyć debuggera w Server Components?
Tak. Uruchom dev server z NODE_OPTIONS='--inspect' i podłącz debugger VS Code do procesu Node. Breakpoint ustawiony w Server Component, Server Action albo Route Handlerze zatrzyma serwer dokładnie na tej linii, dając pełny wgląd w zmienne i stos wywołań i to tak samo jak przy debugowaniu zwykłego kodu backendowego.
Jak sprawdzić, czy komponent jest Server czy Client?
Reguła jest prosta: jeśli plik ma na górze dyrektywę 'use client', to Client Component; jeśli nie ma — w App Routerze jest to domyślnie Server Component. W React DevTools zobaczysz wyłącznie Client Components, bo Server Components nie istnieją w przeglądarce, ponieważ renderują się na serwerze i wysyłają gotowy HTML.
Dlaczego mój console.log nie pojawia się w przeglądarce?
Bo prawdopodobnie jest w Server Component, który wykonuje się na serwerze. W ten sposób log trafia do terminala, w którym uruchomiłeś dev server, a nie do konsoli przeglądarki. To jedna z pierwszych rzeczy, które trzeba sobie przyswoić w App Routerze: logi z kodu serwerowego są w terminalu, a logi z Client Components ('use client') w DevTools przeglądarki.
Dlaczego cache zachowuje się inaczej w dev niż na produkcji?
Bo dev server celowo renderuje strony na świeżo przy każdym żądaniu, żeby zmiany w kodzie były widoczne od razu — statyczne strony, ISR i pełny cache routera zobaczysz dopiero po next build && next start. Jeśli debugujesz „stare dane" w trybie dev, testujesz nie to środowisko: zbuduj aplikację produkcyjnie i sprawdzaj na next start, a symbole ○/ƒ/● w outputcie builda powiedzą Ci, które trasy są w ogóle cachowane.
Co najczęściej powoduje hydration mismatch?
Cztery rzeczy odpowiadają za większość przypadków: użycie new Date() lub Date.now() (serwer i klient liczą inny czas), odwołania do window/document (nie istnieją na serwerze), Math.random() (różny wynik po obu stronach) oraz rozszerzenia przeglądarki modyfikujące DOM (Grammarly, dark mode). Pierwsze trzy rozwiązujesz, przenosząc logikę do useEffect; ostatnie — punktowym suppressHydrationWarning.
O autorze
Maciej Sala
Maciej Sala — Product Manager i Frontend 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 rozwijam własne projekty.
Przez lata React pchał, coraz więcej i więcej pracy do przeglądarki, w efekcie czego mamy większe bundle, hydratacja trwa dłużej, są puste loadingi i strony, które bez JavaScriptu straciły sens. App Router odwraca ten kierunek. React Server Components pozwalają renderować treść na serwerze bez wysyłania całej logiki do klienta, a Server Actions upraszczają formularze i mutacje. To ma znaczenie dla SEO, bo crawler szybciej dostaje HTML. Ma też znaczenie dla biznesu, bo użytkownik szybciej widzi i dostaje to, po co przyszedł.
Maciej Sala
Founder StriveLab
Hydratacja jest momentem, w którym HTML z serwera spotyka się z renderem z JavaScriptu. Jeśli te dwie wersje pokazują inną treść, problem wychodzi poza konsolę i Googlebot może dostać inną stronę niż użytkownik. Czerwony komunikat „Hydration failed” nie powinien być ignorowany, ponieważ błędy hydratacji bardzo negatywnie wpływają na pozycje i SEO.
Maciej Sala
Founder StriveLab
Astro czy Next.js? Wybór frameworka musi być dokładnie przemyślany, zanim pojawi się pierwszy commit. Jeśli stoisz przed takim właśnie wyborem, w tym artykule staram się wykazać, w jakich obszarach najlepiej sprawdza się Astro , a w jakich będzie dominował Next.js .