In Next.js, hydration errors are a common issue when using browser-specific APIs, such as sessionStorage
or localStorage
. One of the most frequent errors developers encounter is:
Error: Text content does not match server-rendered HTML.
Warning: Text content did not match. Server: "2023-2024" Client: "2022-2023"
This error occurs because Next.js renders content on both the server and the client. When the server renders the content, it doesn’t have access to the browser-specific APIs, so it may use default values. However, once the content is hydrated on the client side, these browser APIs become available, and the content may change, leading to a mismatch between the server-rendered and client-rendered HTML.
In this post, we’ll walk through how to use the useEffect
hook to fix this hydration mismatch by ensuring that browser-specific logic, such as accessing sessionStorage
, only runs on the client.
Let's say you have the following code that uses sessionStorage
to save and retrieve the active season of a sports application:
const [activeSeason, setActiveSeason] = useState(initialSeason());
function initialSeason() {
if (typeof window !== "undefined") {
const savedSeason = sessionStorage.getItem("SS-IM-ACTIVE-SEASON");
if (savedSeason) {
return JSON.parse(savedSeason); // Retrieve both season and seasonDisplay
}
}
return {
season: season,
seasonDisplay: `${seasonStart}-${seasonEnd}`, // Default seasonDisplay
seasonStart: seasonStart,
seasonEnd: seasonEnd,
};
}
At first glance, this looks fine, but it will cause a hydration error when the server renders one set of values (e.g., the default season) and the client renders a different value from sessionStorage
. Since the HTML differs, Next.js will throw a hydration mismatch error.
useEffect
The key to solving this issue is to ensure that any browser-specific logic, such as accessing sessionStorage
, only runs on the client side after the component has mounted. This is where useEffect
comes in handy.
useEffect
allows you to delay certain logic until after the component has been rendered on the client, ensuring that the server-rendered HTML matches the initial client-side HTML. Let’s modify the previous code to use useEffect
and prevent the hydration mismatch.
Here’s how to refactor the code:
import { useState, useEffect } from 'react';
const MyComponent = ({ season, seasonStart, seasonEnd }) => {
// Initialize state with the default season for server-side rendering
const [activeSeason, setActiveSeason] = useState({
season: season,
seasonDisplay: `${seasonStart}-${seasonEnd}`,
seasonStart: seasonStart,
seasonEnd: seasonEnd,
});
useEffect(() => {
// Run only on the client side
if (typeof window !== "undefined") {
const savedSeason = sessionStorage.getItem("SS-IM-ACTIVE-SEASON");
if (savedSeason) {
const parsedSeason = JSON.parse(savedSeason);
setActiveSeason(parsedSeason); // Update state with sessionStorage data
}
}
}, []); // Empty dependency array ensures this runs only after the component mounts
return (
<div>
<h1>{activeSeason.seasonDisplay}</h1>
</div>
);
};
export default MyComponent;
useState
hook, we provide a default value for the active season based on props like season
, seasonStart
, and seasonEnd
. This ensures that the server-rendered HTML will always have a consistent value.useEffect
for Client-Side Update:useEffect
hook is used to access sessionStorage
only on the client side. Since useEffect
runs after the component has mounted, it avoids any mismatch during the initial render.Next.js pre-renders the page on the server and sends the HTML to the client. When the page is loaded on the client, React re-renders the component to "hydrate" it, attaching the React event listeners to the HTML. If the HTML on the server and client are different, a mismatch occurs.
By using useEffect
, the sessionStorage
logic is deferred until after the component is mounted on the client side. This ensures the server and client initial renders are consistent, and only after the initial render will the state be updated based on sessionStorage
.
useEffect
: By using the useEffect
hook, you can ensure that browser-specific APIs like sessionStorage
are only accessed on the client side, preventing hydration mismatches.With this approach, you can ensure that your Next.js application works seamlessly with browser APIs like sessionStorage
without causing any hydration mismatch issues.