Przejdź do treści

Hydration mismatch a SEO i indeksowanie w Next.js

Hydration mismatch może zmienić treść widzianą po renderowaniu JavaScriptu. Zobacz, kiedy szkodzi SEO, jak diagnozować rozjazd w Next.js i jak go naprawić.

Maciej Sala

Founder StriveLab

9 min czytaniaAktualizacja

Jak Googlebot przetwarza stronę Next.js z hydration mismatch

Najpierw przyjrzyjmy się, jak Google przetwarza stronę opartą na JavaScripcie. Aktualna dokumentacja opisuje trzy główne etapy: , i . Po pobraniu odpowiedzi analizuje początkowy HTML, między innymi wykrywa w nim linki, a stronę z kodem 200 kieruje do kolejki renderowania.

Kiedy zasoby są dostępne, Web Rendering Service uruchamia JavaScript i przekazuje wyrenderowany HTML do dalszego przetwarzania oraz indeksowania. Nie jest to więc prosty model dwóch osobnych indeksacji, w którym Google najpierw zapisuje wersję serwerową, a potem ją zastępuje. Początkowy HTML nadal ma znaczenie: przyspiesza dostęp do treści, pomaga crawlerom, które nie wykonują JavaScriptu, i ogranicza zależność od poprawnego pobrania oraz uruchomienia skryptów. W aplikacji renderowanej wyłącznie po stronie klienta () początkowa odpowiedź może zawierać tylko app shell.

Next.js ogranicza tę zależność, ponieważ i mogą dostarczyć gotowy HTML z treścią. Zaleta tego podejścia opiera się jednak na ważnym założeniu: HTML z serwera i pierwszy render klienta są zgodne. Hydration mismatch łamie to założenie. Jego znaczenie dla SEO zależy od tego, co zmieni się w końcowym DOM.

W App Routerze warto dodatkowo rozróżnić Server Components i Client Components. Server Components nie są hydratowane w przeglądarce. React hydratuje interaktywne granice oznaczone przez 'use client', choć także one mogą zostać wcześniej wyrenderowane do HTML. Dlatego ograniczenie liczby oraz rozmiaru tych granic zmniejsza powierzchnię, na której może wystąpić mismatch.

Co dzieje się z SEO, gdy serwer i klient renderują różną treść

React podczas oczekuje, że drzewo zbudowane na kliencie będzie zgodne z serwerowym HTML. W React 19 błąd może spowodować ponowne wygenerowanie dotkniętego drzewa po stronie klienta. Nie należy zakładać, że React bezpiecznie poprawi każdą różnicę: dokumentacja nie gwarantuje nawet naprawienia wszystkich rozbieżnych atrybutów. I tu może zacząć się problem dla wyszukiwarki oraz użytkownika.

Wyobraźmy sobie kartę produktu, gdzie serwer renderuje status „niedostępny” na podstawie danych z momentu budowania strony, a klient po hydratacji odpytuje świeże API i podmienia go na „dostępny”. Google może zobaczyć zmianę podczas renderowania JavaScriptu. Sam taki rozjazd nie jest jednak : Google definiuje cloaking jako celowe pokazywanie wyszukiwarce i użytkownikom różnych treści w celu manipulowania wynikami. Problemem jest tutaj nieaktualny lub niestabilny stan danych, który może wprowadzać w błąd użytkownika i utrudniać wyszukiwarce jednoznaczną interpretację strony.

Ten sam mechanizm może uderzyć w dane strukturalne. Jeżeli z ceną, dostępnością czy oceną nie odpowiada treści widocznej po renderowaniu, strona może stracić kwalifikację do . Nie oznacza to automatycznie spadku zwykłej pozycji ani deindeksacji, ale możesz stracić gwiazdki, cenę lub informację o dostępności w wynikach. Warto też pamiętać, że hydration mismatch bywa objawem szerszego problemu: jeśli ponowny render przesuwa layout albo opóźnia gotowość interfejsu, może pogorszyć doświadczenie użytkownika i metryki wydajnościowe. To osobny wątek, który rozkładamy na czynniki w artykule o wzorcach niszczących INP w React.

Skąd bierze się hydration mismatch w Next.js

Niemal każdy hydration mismatch sprowadza się do kodu, który na serwerze i na kliencie daje inny rezultat. Trzy źródła odpowiadają za większość przypadków.

Pierwsze to odwołania do API przeglądarki, których na serwerze nie ma. Bezpośrednie użycie window, document albo localStorage podczas SSR zwykle kończy się błędem wykonania, a nie samym hydration mismatch. Klasyczny rozjazd powstaje wtedy, gdy kod ma osobną gałąź serwerową i kliencką, a każda z nich zwraca inną treść.

Code
// ŹLE: serwer zwraca "light", a pierwszy render klienta może zwrócić "dark"
function ThemeBadge() {
  const theme =
    typeof window === 'undefined'
      ? 'light'
      : (localStorage.getItem('theme') ?? 'light')
 
  return <span>Motyw: {theme}</span>
}

Drugie źródło to wartości zmienne w czasie i losowość, czyli Date.now(), new Date(), Math.random() czy generowane identyfikatory. Serwer policzy je w momencie renderu, a klient w momencie hydratacji. Efekt? Niemal nigdy nie wyjdzie to samo.

Code
// ŹLE: timestamp jest inny w każdym renderze
function RenderTime() {
  return <time>{Date.now()}</time>
}

Trzecie to treść zależna od stanu, który istnieje tylko po stronie klienta. Mogą to być preferencje z ciasteczek odczytywane dopiero w przeglądarce, dane z localStorage albo warunki oparte na szerokości ekranu. Wspólny mianownik wszystkich trzech jest ten sam, czyli pierwszy render klienta musi być identyczny z HTML serwera, a tutaj nie jest.

Trzeba przy tym zaznaczyć jedną rzecz: nie każdy komunikat „Hydration failed” niesie takie samo ryzyko SEO. Część rozjazdów bierze się z niepoprawnie zagnieżdżonego HTML, na przykład <p> wewnątrz <p> albo <div> w <p>, które przeglądarka naprawia inaczej niż renderer Reacta. Ten przypadek dał mi się we znaki przy pierwszym projekcie w Next.js: błąd wracał uparcie, a jego źródłem za każdym razem był źle zagnieżdżony tag, nie logika danych. Inną przyczyną mogą być rozszerzenia przeglądarki, które modyfikują DOM przed hydratacją. Taki lokalny błąd zwykle nie dotyczy Googlebota, ale trzeba odróżnić go od problemu występującego u wszystkich użytkowników.

Błędy pochodzące z aplikacji warto naprawić nawet wtedy, gdy nie zmieniają tekstu. Rozjazd może dotyczyć również linków, atrybutów lub interaktywności, a React nie gwarantuje poprawienia każdego z nich. Z perspektywy SEO najwyższy priorytet mają problemy, które zmieniają główną treść, linki, metadane, dane strukturalne albo kończą się niepełnym renderem.

Jak sprawdzić, czy mismatch rzeczywiście szkodzi SEO

Najpierw odtwórz problem w trybie produkcyjnym przez next build i next start. Tryb deweloperski daje lepszy komunikat i stos komponentów, ale produkcyjny render najwierniej pokazuje zachowanie wdrożenia. Porównaj dokument HTML zwrócony przez serwer z DOM-em po wykonaniu JavaScriptu i sprawdź przede wszystkim nagłówek, główną treść, linki, canonical, dyrektywy robots oraz JSON-LD.

Następnie użyj URL Inspection w Google Search Console albo Rich Results Test. Oba narzędzia pozwalają obejrzeć wyrenderowany HTML, załadowane zasoby i wyjątki JavaScript widziane przez środowisko Google. Nie diagnozuj SEO wyłącznie na podstawie ostrzeżenia w lokalnym DevTools — ustal, czy ten sam błąd występuje bez rozszerzeń przeglądarki i czy zmienia elementy, które Google ma przetworzyć.

W większym projekcie warto dodatkowo monitorować błędy hydratacji w produkcji i dodać test end-to-end, który otwiera kluczowe podstrony oraz kończy się niepowodzeniem po wykryciu komunikatu hydratacji w konsoli.

Jak naprawić hydration mismatch w Next.js bez psucia SEO

Trzymaj się zasady, by kod zależny od środowiska klienta wykonał się dopiero po hydratacji, a nie w trakcie pierwszego renderu. W praktyce oznacza to przeniesienie takiej logiki do useEffect, który React uruchamia wyłącznie na kliencie, już po dopasowaniu drzewa do serwerowego HTML.

Code
// DOBRZE, serwer i pierwszy render klienta są zgodne, motyw doczytujemy po hydratacji
'use client'
 
import { useEffect, useState } from 'react'
 
function ThemeBadge() {
  const [theme, setTheme] = useState('light')
 
  useEffect(() => {
    setTheme(localStorage.getItem('theme') ?? 'light')
  }, [])
 
  return <span>Motyw: {theme}</span>
}

Tutaj serwer oraz pierwszy render klienta pokazują light, więc nie powstaje mismatch. Dopiero później useEffect odczytuje preferencję i aktualizuje element interfejsu. Ten wzorzec może jednak powodować krótkie mignięcie niewłaściwego motywu. Jeśli wartość jest potrzebna już w początkowym HTML, lepiej zapisać ją w ciasteczku, odczytać na serwerze i przekazać do Client Component jako początkowy stan.

Gdy cały fragment ma się pojawić dopiero w kliencie, ten sam mechanizm wygodnie zamknąć we flagę montowania. Komponent renderuje wtedy na serwerze i w pierwszym przebiegu klienta dokładnie to samo, czyli nic albo neutralny placeholder, a właściwą treść pokazuje dopiero po hydratacji:

Code
// DOBRZE, do pierwszego renderu klient i serwer zwracają to samo (null)
'use client'
 
import { type ReactNode, useEffect, useState } from 'react'
 
function ClientOnly({ children }: { children: ReactNode }) {
  const [mounted, setMounted] = useState(false)
 
  useEffect(() => setMounted(true), [])
 
  if (!mounted) return null
  return <>{children}</>
}

A co z danymi spoza Reacta, takimi jak matchMedia czy status sieci? Jeśli komponent ma reagować na zmiany zewnętrznego, subskrybowalnego źródła, użyj useSyncExternalStore. Hook przyjmuje subscribe, getSnapshot i — podczas SSR — getServerSnapshot. Wartość zwrócona przez getServerSnapshot musi być taka sama na serwerze i podczas pierwszego renderu klienta. Do jednorazowego odczytu localStorage często wystarczy prostszy wzorzec z useEffect; useSyncExternalStore ma sens dopiero wtedy, gdy naprawdę subskrybujesz zmiany.

Istnieją też komponenty, które z natury nie mają sensu na serwerze i może to być na przykład interaktywna mapa, wykres albo cokolwiek, co opiera się o obiekt window. W takim wypadku najprostszym i najczystszym rozwiązaniem jest całkowite wyłączenie dla nich renderowania po stronie serwera (SSR) za pomocą next/dynamic:

Code
// Komponent renderowany wyłącznie na kliencie, bez serwerowego HTML do rozjazdu
'use client'
 
import dynamic from 'next/dynamic'
 
const Map = dynamic(() => import('./Map'), { ssr: false })

W App Routerze opcja ssr: false działa tylko w Client Component. Nie można umieścić jej bezpośrednio w Server Component, dlatego granica 'use client' w tym przykładzie jest istotna.

Pamiętaj tylko, że taki komponent znika z początkowego HTML, więc trzymaj w nim wyłącznie rzeczy, które nie są ważne dla indeksacji. Mogą to być interaktywne widżety, a nie treść, którą ma zobaczyć Googlebot.

W sytuacjach, w których fragment naprawdę musi różnić się między serwerem a klientem i nie da się tego uniknąć, React daje punktowe rozwiązanie:

Code
// Świadomy, lokalny wyjątek tylko dla treści bez znaczenia dla SEO
<time suppressHydrationWarning>{new Date().toLocaleTimeString()}</time>

suppressHydrationWarning wycisza ostrzeżenie w tym jednym węźle i działa tylko jeden poziom w głąb. Nie naprawia przyczyny, a React nie próbuje podczas hydratacji poprawiać objętego nim rozbieżnego tekstu. To escape hatch dla nieuniknionych różnic, takich jak znacznik czasu, a nie sposób na ukrywanie błędu w treści przeznaczonej do indeksowania.

Dlaczego dynamic rendering nie jest rozwiązaniem długoterminowym

Kiedyś popularnym obejściem problemów JavaScriptu i SEO było , czyli wykrywanie bota i podawanie mu osobnej, prerenderowanej wersji strony. Google opisuje je obecnie jako obejście, a nie rekomendowane rozwiązanie długoterminowe. Utrzymywanie dwóch osobnych ścieżek renderowania zwiększa koszt i ryzyko niespójności treści.

Aktualne podejście w Next.js i React jest prostsze oraz spójniejsze. SSR albo SSG z poprawną hydratacją dostarcza tę samą, kompletną treść użytkownikowi i wyszukiwarce, bez rozdzielania ścieżek. W praktyce oznacza to, że treść ważna dla SEO ma znaleźć się w początkowym HTML, a hydratacja nie może jej podmieniać. Przy większym projekcie contentowym sprawdź też, kiedy Next.js naprawdę daje przewagę SEO nad zwykłym Reactem, bo dobrze ustawiona hydratacja jest częścią tej przewagi, a nie czymś osobnym.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Czym jest hydration mismatch?

Hydration mismatch to rozjazd między HTML wygenerowanym na serwerze a tym, co React próbuje wyrenderować na kliencie podczas hydratacji. Jeśli oba drzewa się nie zgadzają, bo np. komponent użył Date.now() albo zwrócił inną wartość po sprawdzeniu window lub localStorage, React zgłasza błąd i może ponownie wygenerować dotknięte drzewo po stronie klienta. Dla SEO znaczenie ma to, czy błąd zmienia treść lub inne elementy przetwarzane przez wyszukiwarkę.

Czy Googlebot widzi błędy hydratacji w konsoli?

Sam komunikat w konsoli nie jest sygnałem rankingowym. Google może jednak zarejestrować wyjątek podczas renderowania, a przede wszystkim widzi jego skutek w wyrenderowanym HTML. Problem SEO pojawia się wtedy, gdy błąd usuwa lub zmienia istotną treść, linki, metadane albo dane strukturalne.

Czy hydration mismatch może spowodować deindeksację?

Nie bezpośrednio. Hydration mismatch nie jest samodzielną przyczyną deindeksacji ani automatycznie cloakingiem. Może natomiast pośrednio utrudnić indeksowanie, jeśli po wykonaniu JavaScriptu strona jest pusta, pokazuje błąd, traci główną treść lub przypomina soft 404.

Jak naprawić hydration mismatch w Next.js?

Najpierw ustal źródło rozjazdu: kod zależny od window, document, Date, losowości albo localStorage, który na serwerze daje inny wynik niż na kliencie. Wartości czysto klienckie możesz odczytać w useEffect, a dane potrzebne w HTML najlepiej pobrać na serwerze i przekazać jako stabilny snapshot. suppressHydrationWarning stosuj wyłącznie jako punktowy wyjątek, a nie sposób naprawiania treści ważnej dla SEO.

Czy dynamic rendering to dobre rozwiązanie problemów z JavaScriptem i SEO?

Google traktuje dynamic rendering jako obejście, a nie rekomendowane rozwiązanie długoterminowe. Preferowane są SSR, renderowanie statyczne lub poprawna hydratacja. Osobna wersja dla botów zwiększa koszt utrzymania i ryzyko niespójności.

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
Kiedy Next.js daje przewagę w SEO nad React SPA

Next.js to framework React, który ułatwia uniknięcie głównego ograniczenia aplikacji SPA renderowanych wyłącznie w przeglądarce: kluczowa treść może znaleźć się już w początkowym HTML. Google nie musi wtedy czekać na wykonanie JavaScriptu, żeby ją odkryć. Framework dodaje też Metadata API, optymalizację obrazów i podział kodu, ale korzyść dla Core Web Vitals zależy od sposobu implementacji, ilości kodu klienckiego i infrastruktury.

Maciej Sala

Maciej Sala

Founder StriveLab

Jak debugować Next.js? Cache i Hydration Errors

W Create React App wszystko działa w przeglądarce — console.log , React DevTools, Chrome DevTools. W Next.js App Router kod wykonuje się w trzech miejscach: na serwerze Server Components, Server Actions , w warstwie proxy i na kliencie Client Components . Dlatego console.log w Server Component pojawia się w terminalu, a nie w przeglądarce — i to dopiero początek różnic, które trzeba mieć w głowie.

Maciej Sala

Maciej Sala

Founder StriveLab

Jak przeprowadzić audyt techniczny SEO w React i Next.js

Audyt techniczny SEO odpowiada na jedno pytanie: czy wyszukiwarki, a w 2026 coraz częściej także silniki AI, potrafią dotrzeć do Twojej strony, wyrenderować ją, zrozumieć i dodać do indeksu? Na tym założeniu stoi cała reszta: treść, linki, konwersja. Patrzę na nią z perspektywy frontend dewelopera, a nie tylko specjalisty SEO, ponieważ w React i Next.js prawdziwe problemy zaczynają się w renderowaniu, hydratacji i w tym, co crawler widzi w surowym HTML-u, zanim wykona JavaScript.

Maciej Sala

Maciej Sala

Founder StriveLab

LH.pl – Cloud Server 1C4G