Przejdź do treści

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

Googlebot widzi serwerowy HTML, a nie stan po hydracji. Gdy Next.js się "rozjeżdża", SEO cierpi. Dowiedz się, co naprawdę indeksuje Google i jak to naprawić.

Maciej Sala

Founder StriveLab

8 min czytaniaOpublikowano 30 maja 2026 (Aktualizacja 6 lipca 2026)

Jak Googlebot czyta stronę Next.js z hydration mismatch

Najpierw przyglądnijmy się, jak Google przetwarza stronę opartą na JavaScripcie. Proces przebiega w . W pierwszej pobiera surowy HTML zwrócony przez serwer i od razu wyciąga z niego treść, linki oraz metadane. Wszystko, co znajdzie na tym etapie, może trafić do indeksu błyskawicznie i bez czekania na render JS.

W drugiej fali robot wraca, uruchamia JavaScript i analizuje wyrenderowany DOM. Ten etap może trochę potrwać, nawet do kilku godzin, czasem kilku dni. Wszystko zależy od kolejki renderowania. Dlatego treść obecna w początkowym HTML indeksuje się najszybciej i najpewniej, a treść pojawiająca się dopiero po JS zależy od drugiej fali. W aplikacji renderowanej tylko po stronie klienta () robot w pierwszym podejściu widzi pustą skorupę.

Next.js z założenia rozwiązuje ten problem, ponieważ i dostarczają gotowy HTML z treścią już w pierwszej fali. Najmocniejsza zaleta tego podejścia opiera się jednak na jednym, ważnym założeniu: HTML z serwera i render klienta są ze sobą zgodne. Hydration mismatch jest niebezpieczny, ponieważ łamie to założenie i może poważnie zaszkodzić indeksacji i stabilności w wynikach wyszukiwania.

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

React podczas oczekuje, że drzewo, które zbuduje na kliencie, będzie identyczne z tym, które otrzymał w serwerowym HTML. Gdy napotka różnicę, w zależności od jej skali albo naprawia pojedyncze miejsce, albo — przy poważniejszym rozjeździe — odrzuca serwerowy HTML i renderuje dany fragment od nowa po stronie klienta. I tu zaczyna się problem dla wyszukiwarki.

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". Googlebot w pierwszej fali zaindeksuje wersję serwerową. Jeśli render klienta konsekwentnie pokazuje co innego, robot trafia na niespójność między zaindeksowanym HTML a tym, co widzi po renderze — a w skrajnym przypadku może to zostać odczytane jako . Udokumentowano przypadki, w których właśnie taki rozjazd kończył się wypadnięciem strony z indeksu.

Ten sam mechanizm uderza w dane strukturalne, ponieważ jeśli z ceną, dostępnością czy oceną renderuje się inaczej na serwerze, a inaczej po hydratacji, Google może uznać za niezgodne z widoczną treścią i przestać przyznawać . W efekcie tracisz gwiazdki, cenę czy informację o dostępności w wyszukiwarce. Strona technicznie nadal się indeksuje, ale Google przestaje "ufać" danym strukturalnym, co pozbawia Cię kluczowej przewagi wizualnej nad konkurencją. Warto też pamiętać, że hydration mismatch bywa objawem szerszego problemu: jeśli podmiana treści przesuwa layout, odbija się to na — ale to już osobny, wydajnościowy 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 po prostu nie ma. Komponent sięgający po window, document albo localStorage na serwerze dostaje wartość domyślną lub błąd, a na kliencie prawdziwe dane. Efektem jest rozjazd drzew.

Code
// ŹLE — localStorage nie istnieje na serwerze, render serwera i klienta się różnią
function ThemeBadge() {
  const theme = localStorage.getItem('theme') ?? 'light'
  return <span>Motyw: {theme}</span>
}

Drugie źródło to wartości zmienne w czasie i losowość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 — godzina policzona na serwerze różni się od tej na kliencie
function Greeting() {
  const hour = new Date().getHours()
  return <p>{hour < 12 ? 'Dzień dobry' : 'Dobry wieczór'}</p>
}

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żde „Hydration failed" ma związek z treścią ważną dla SEO. Część rozjazdów bierze się z niepoprawnie zagnieżdżonego HTML — klasyk to <p> wewnątrz <p> albo <div> w <p>, które przeglądarka po cichu naprawia inaczej niż serwer. Akurat ten przypadek dał mi się we znaki przy pierwszym projekcie w Next.js: błąd wracał uparcie, a okazało się, że źródłem był, za każadym razem, źle zagnieżdżony tag - nie żadna logika danych. Inną częstą przyczyną są rozszerzenia przeglądarki, które dorzucają atrybuty do DOM, zanim React zdąży się zhydratyzować.

Te wszystkie przypadki trzeba naprawić, ponieważ psują render, ale nie zmieniają treści, którą indeksuje Google. Z perspektywy SEO groźne są wyłącznie te rozjazdy, które podmieniają widoczną treść — i właśnie na nich skup się w pierwszej kolejności.

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

Najczystsza zasada brzmi: kod zależny od środowiska klienta powinien 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
function ThemeBadge() {
  const [theme, setTheme] = useState('light')
 
  useEffect(() => {
    setTheme(localStorage.getItem('theme') ?? 'light')
  }, [])
 
  return <span>Motyw: {theme}</span>
}

Tutaj serwer i pierwszy render klienta pokazują light, więc hydratacja przebiega bezproblemowo. Dopiero po niej useEffect odczytuje prawdziwą preferencję i aktualizuje widok. Z perspektywy SEO to bezpieczne, bo treść używana do indeksacji — jeśli taka jest w tym miejscu — pozostaje stabilna, a po hydratacji zmienia się jedynie element czysto interfejsowy.

Gdy cały fragment ma się pojawić dopiero na kliencie, ten sam mechanizm wygodnie zamknąć we flagę montowania. Komponent renderuje wtedy na serwerze i w pierwszym przebiegu klienta dokładnie to samo — 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)
function ClientOnly({ children }: { children: React.ReactNode }) {
  const [mounted, setMounted] = useState(false)
 
  useEffect(() => setMounted(true), [])
 
  if (!mounted) return null
  return <>{children}</>
}

A co z danymi spoza Reacta, takimi jak localStorage, matchMedia czy status sieci? W React 18 i nowszych wersjach najlepiej użyć do tego hooka useSyncExternalStore. Dzięki niemu możemy z góry powiedzieć, co ma wyrenderować serwer, więc jego praca jest przewidywalna. W przeglądarce komponent sam nasłuchuje na właściwą wartość, bez potrzeby pisania skomplikowanej logiki w useEffect.

Są też komponenty, które z natury nie mają sensu na serwerze. 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 — żadnego serwerowego HTML do rozjazdu
const Map = dynamic(() => import('./Map'), { ssr: false })

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 mówi Reactowi, żeby zaakceptował różnicę w tym jednym węźle. To narzędzie do stosowania wyłącznie tam, gdzie rozbieżność jest nieunikniona i dotyczy treści, na której wyszukiwarce nie zależy, jak zegar czy znacznik czasu. Nigdy nie używaj go, żeby „uciszyć" rozjazd na treści, która ma się indeksować.

Dlaczego dynamic rendering to już ślepa uliczka

Kiedyś popularnym obejściem problemów JavaScriptu i SEO było , czyli wykrywanie bota i podawanie mu osobnej, prerenderowanej wersji strony. Google traktuje to dziś jako rozwiązanie tymczasowe, które zostało wycofane z rekomendacji. Powód jest prosty: utrzymywanie dwóch osobnych ścieżek renderowania samo w sobie rodzi ryzyko rozjazdu treści — czyli dokładnie tego problemu, który próbujemy wyeliminować.

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 — 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(), window albo danych z localStorage — React zgłasza błąd, a w skrajnych przypadkach odrzuca serwerowy HTML i renderuje stronę od nowa po stronie klienta. Z perspektywy SEO problem nie jest kosmetyczny, ponieważ wpływa na to, jaką treść ostatecznie widzi wyszukiwarka.

Czy Googlebot widzi błędy hydratacji w konsoli?

Nie wprost — Googlebot nie czyta ostrzeżeń z konsoli. Widzi jednak ich skutek. W pierwszej fali indeksuje surowy HTML z serwera, a w drugiej renderuje stronę z JavaScriptem. Jeśli hydratacja zmienia treść względem serwerowego HTML, robot może zaindeksować jedną wersję, a użytkownik zobaczyć inną. To właśnie ten rozjazd, a nie sam komunikat w konsoli, bywa problemem dla pozycji.

Czy hydration mismatch może spowodować deindeksację?

W skrajnych przypadkach tak. Jeśli serwer renderuje jedną treść (np. „niedostępny"), a klient po hydratacji podmienia ją na inną (np. „dostępny"), Google może uznać to za niespójność między tym, co zaindeksował z HTML, a tym, co widzi po renderze — a w najgorszym scenariuszu za przejaw cloakingu. Udokumentowano przypadki, w których taki rozjazd prowadził do wypadnięcia strony z indeksu. To rzadki scenariusz, ale realny — i dlatego warto go wykluczyć już na etapie kodu.

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 przenieś do useEffect, żeby wykonały się dopiero po hydratacji. Dla treści, która naprawdę musi różnić się na serwerze i kliencie, użyj wzorca z flagą montowania albo punktowo suppressHydrationWarning. Zasada jest prosta: treść ważna dla SEO powinna być taka sama w HTML serwera i po hydratacji.

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

Już nie. Dynamic rendering — serwowanie osobnej, prerenderowanej wersji robotom — Google traktuje jako obejście, które zostało wycofane jako rekomendacja. Współczesnym standardem jest SSR lub SSG z poprawną hydratacją, gdzie ta sama, kompletna treść trafia i do użytkownika, i do wyszukiwarki. Zamiast budować osobną ścieżkę dla botów, zadbaj, żeby treść ważna dla SEO znajdowała się w początkowym HTML.

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
Audyt techniczny SEO w React i Next.js: Jak znaleźć błędy, które niszczą widoczność?

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

Next.js a SEO — kiedy naprawdę daje przewagę nad zwykłym Reactem

Next.js to framework React, który rozwiązuje największy problem klasycznych aplikacji SPA : serwer wysyła gotowy HTML zamiast pustej strony. Dzięki temu Google widzi treść w początkowym HTML i nie musi czekać na JavaScript, żeby odkryć najważniejsze elementy strony. Do tego dostajesz wbudowane API do metadanych, automatyczną optymalizację obrazów i code splitting podział kodu ładowanego osobno dla każdej podstrony , który realnie poprawia Core Web Vitals.

Maciej Sala

Maciej Sala

Founder StriveLab

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