Struggling with react-hydration-error in your Next.js app? This article breaks down how Hydration works in React, explains the difference between SSR and CSR in Next.js, and shares three practical solutions to fix Hydration Errors for smoother, error-free rendering.
Written by: Chia1104 CC BY-NC-SA 4.0

When developing NextJS projects, you've probably encountered errors like react-hydration-error.
Before explaining how to fix this issue, we need to first understand how NextJS handles SSR (Server-Side Rendering) and CSR (Client-Side Rendering).
We all know that NextJS is a full-stack framework built on React, enabling SSR the React way.
However, unlike traditional full-stack frameworks such as PHP's Laravel, NextJS only pre-generates HTML for the very first page a user visits to perform SSR.
When navigating to other pages afterward, it behaves like a typical SPA (Single-Page Application) under the CSR concept — it loads only the necessary components without reloading the entire page. (NextJS Client-side Navigation)
React provides server-side APIs to generate HTML, one of which is renderToPipeableStream.
This method allows React to gradually generate HTML and stream it to the client through a Node.js Stream.
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});Process Overview
We mentioned earlier that the first render is static HTML. But how does it become interactive through React APIs like useState?
After completing SSR and sending static HTML to the client, React can use hydrateRoot to turn that HTML into interactive React elements.
const root = hydrateRoot(domNode, reactNode)domNode: Corresponds to the root element in the pre-rendered HTML.reactNode: The React component that generated that HTML.import { hydrateRoot } from "react-dom/client";
import App from "./App";
hydrateRoot(document.getElementById("root"), <App />);This process of combining pre-rendered HTML from the server with the client-side React app — giving it interactivity - is called Hydration.
From the hydrateRoot method, you can see that it runs on the client side to render the UI. At the same time, NextJS uses renderToPipeableStream to pre-render HTML during SSR.
The content passed to hydrateRoot must exactly match the pre-rendered HTML; otherwise, a react-hydration-error occurs.
As seen in the process above, React ensures that the HTML generated on both server and client sides match. If they differ when calling hydrateRoot, a Hydration Error is triggered.
The most common example is time handling — especially time zones: The server usually runs in UTC (+0), but the user's browser follows the local time zone.

Besides time zone issues, using browser-only APIs like localStorage can also trigger Hydration Errors, since the server doesn't have access to localStorage.
useEffect to Detect Client Side"use client";
import { useState, useEffect } from 'react'
export default function App() {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return <h1>{isClient ? 'This is never prerendered' : 'Prerendered'}</h1>
}Since useEffect runs only in the browser environment, the initial isClient value (false) remains the same in both server and client DOM trees — avoiding Hydration Errors.
The key is ensuring initial values on both server and client match.
dynamic with ssr: falseYou can use NextJS's dynamic API to mark components that don't need pre-rendering. That means both SSR rendering and client hydration are skipped.
import dynamic from 'next/dynamic'
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
export default function Page() {
return (
<div>
<NoSSR />
</div>
)
}suppressHydrationWarningThis prop suppresses Hydration Error warnings. It's commonly used with libraries like next-themes. When you have DOM elements that depend on client-side dynamic values unavailable on the server, you can use this prop to silence mismatched hydration warnings.
<html colorSchema="dark" suppressHydrationWarning />