Przejdź do treści

Jak debugować Next.js? Cache i Hydration Errors

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 komponencie
import 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 cache
const 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ź revalidate
export 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.js
rm -rf .next
 
# Restart dev servera
npm run dev

Debugowanie revalidation w Next.js

Code
// Dodaj logi do revalidation
import { 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 sekund
export 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ądarce
export 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>
}

VS Code debugger dla React Server Components

Code
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "name": "Next.js: debug client-side",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000"
    },
    {
      "name": "Next.js: debug full stack",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/next",
      "args": ["dev"],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal"
    }
  ]
}
Code
# Uruchom Next.js z debuggerem
NODE_OPTIONS='--inspect' npm run dev

Teraz możesz ustawiać breakpointy w Server Components, Server Actions i Route Handlers — VS Code zatrzyma się na nich.

Problem 4: Server Actions i ciche błędy

Server Actions mogą failować bez widocznego błędu na UI, jeśli nie obsługujesz wyniku:

Code
// ŹLE — cichy fail
;<form action={deletePost}>
  <button type="submit">Usuń</button>
</form>
 
// DOBRZE — obsłuż wynik
;('use client')
 
import { useActionState } from 'react'
 
function DeleteButton({ postId }: { postId: string }) {
  const [state, formAction, isPending] = useActionState(
    // Akcja dostaje (poprzedni stan, formData); to co ZWRÓCI, ląduje w state
    async () => deletePost(postId), // zwraca { success, error? }
    null,
  )
 
  return (
    <form action={formAction}>
      <button disabled={isPending}>{isPending ? 'Usuwanie...' : 'Usuń'}</button>
      {state && !state.success && <p className="text-red-500">{state.error}</p>}
    </form>
  )
}

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:

Code
// middleware.ts (w Next.js 16: proxy.ts)
export function middleware(request: NextRequest) {
  const start = Date.now()
 
  // ... logika
 
  const duration = Date.now() - start
  console.log(
    JSON.stringify({
      type: 'middleware',
      path: request.nextUrl.pathname,
      method: request.method,
      duration: `${duration}ms`,
      userAgent: request.headers.get('user-agent')?.slice(0, 50),
    }),
  )
 
  return response
}

Narzędzia do debugowania Next.js

React DevTools: Components i Profiler

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):

Code
// next.config.ts
const nextConfig = {
  logging: {
    fetches: {
      fullUrl: true, // pełne URL-e zamiast skróconych
    },
  },
}

Dopiero gdy potrzebujesz czegoś więcej (czasy, własny format, fetch spoza Next.js), sięgnij po ręczny interceptor:

Code
// Globalny fetch logger (development only)
if (process.env.NODE_ENV === 'development') {
  const originalFetch = global.fetch
  global.fetch = async (...args) => {
    const url = typeof args[0] === 'string' ? args[0] : (args[0] as Request).url
    console.log(`[FETCH] ${url}`)
    const start = Date.now()
    const result = await originalFetch(...args)
    console.log(`[FETCH] ${url}${result.status} (${Date.now() - start}ms)`)
    return result
  }
}
Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Jak debugować Next.js na produkcji?

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.

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

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Jak React Server Components wpływają na SEO i performance?

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

Maciej Sala

Founder StriveLab

Hydration Mismatch a SEO: co widzi Googlebot? Next.js pod lupą

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

Maciej Sala

Founder StriveLab

Astro vs Next.js w 2026: Porównanie frameworków

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 .

Maciej Sala

Maciej Sala

Founder StriveLab