Loading

What causes a hydration mismatch in Next.js, and how do you debug and fix it?

Asked by Md Sharifuzzaman Shajib · · 7 answers · Solved

A hydration mismatch in Next.js happens when server-rendered HTML doesn't match the client-rendered version, causing React to throw errors. It’s often due to non-deterministic rendering or client-only code running on the server.

7 answers

Accepted answer · Answered by Md Sharifuzzaman Shajib ·

Using useEffect to Change Content Immediately

❌ Problem:

useEffect(() => {
  setValue('Updated');
}, []);

If the server renders 'Initial', but client quickly updates it to 'Updated', mismatch.

✅ Fix:

Avoid rendering different states initially. Show fallback:

const [value, setValue] = useState(null);

useEffect(() => {
  setValue('Updated');
}, []);

if (value === null) return <Loading />;

Answered by Fozle Rabbi ·

Mismatched Third-party Libraries (like date formatting)

Cause:

Libraries like moment or dayjs can show locale-based or timezone-sensitive data.

❌ Problem Code:

import dayjs from 'dayjs';

export default function Page() {
  return <div>{dayjs().format('HH:mm')}</div>;
}

✅ Fix:

Render such time-based data only on client:

'use client';

Or use server-time consistently and cache the value.

Answered by Sahin Khan ·

Inconsistent Environment Variables

Cause:

Rendering depends on env variables that differ in server vs. browser.

❌ Example:

<div>{process.env.NEXT_PUBLIC_TITLE}</div>

If not prefixed with NEXT_PUBLIC_, they won’t be available on client.

✅ Fix:

Always prefix env vars for client usage:

NEXT_PUBLIC_TITLE=MyApp

Answered by Dipa Saha ·

Using window or document in Server Components

❌ Problem Code:

export default function Page() {
  return <div>Width: {window.innerWidth}</div>;
}

This breaks during SSR, as window is undefined.

✅ Fix:

'use client';
import { useEffect, useState } from 'react';

export default function Page() {
  const [width, setWidth] = useState(0);
  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);
  return <div>Width: {width}</div>;
}

Answered by Mynul Islam ·

Server and client generate different values.

❌ Problem Code:

export default function Page() {
  return <div>Random number: {Math.random()}</div>;
}

✅ Fix:

Use such logic only on the client:

'use client';
import { useEffect, useState } from 'react';

export default function Page() {
  const [random, setRandom] = useState(0);
  useEffect(() => {
    setRandom(Math.random());
  }, []);
  return <div>Random number: {random}</div>;
}

Answered by Ratul Sikder ·

Rendering Based on navigator, localStorage, etc.

❌ Problem:

const isDark = localStorage.getItem('theme') === 'dark';

This causes crash/mismatch on server.

✅ Fix:

'use client';
import { useEffect, useState } from 'react';

export default function Page() {
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    setTheme(localStorage.getItem('theme') || 'light');
  }, []);

  return <div>Current theme: {theme}</div>;
}

Answered by Shaykot Hossain Selim ·

Cause:

If server and client fetch data differently, mismatch can happen.

❌ Problem Code:

export default async function Page() {
  const res = await fetch('<https://api.example.com/data>');
  const { message } = await res.json();
  return <div>{message}</div>;
}

If data changes between SSR and client hydration, mismatch occurs.

✅ Fix:

Use caching (e.g., fetch with next/cache) or move to client:

// Use revalidation and caching
export const dynamic = 'force-static';

Or defer to the client if live data is essential:

'use client';

Browse more questions from the manob.ai community