NextJS is powerful: it brings together the best of server-side to the best of client-side. You can build powerful applications written in React, and produce a static website which has all the speed of a regular ol’ CDN-backed website. Despite the benefits, you may sometimes hit some challenges working with both server-side rendering (SSR) and client-side hydration. One of the more common errors is when the server-side and client-side DOM doesn’t match. NextJS might complain:
Expected server HTML to contain a matching <div> in <div>
Warning: Text content did not match. Server: "Log in" Client: "Continue to dashboard"
Additionally, the HTML (in particular, the DOM elements) may not render correctly when viewing the webpage.
Why is this happening?
NextJS uses both server-side and client-side rendering together. The server serves static HTML whenever possible, and the client hydrates it to give full interactivity. If you manipulate the DOM at any time between when the server-side HTML is served and the client-side is rendered, there will be a mismatch and React will not be able to hydrate the DOM successfully. Thus, the first render of any page must match the initial render of the server. This is explained more in-depth in this Github issue. I encountered this issue when relying on cookies. I was doing something like this:
const user = Cookies.get('jwt')
if (user?.id) {
return <span>User logged in!</span>
}
return <span>User not logged in!</span>
Cookies are only accessible in the browser, so when the server provides the static HTML the user would not be logged in, but when the client is rendered, the user would be logged in! Hence, there is a mismatch and NextJS will complain.
How do I fix this?
Put all browser-only code inside useEffect
. This ensures that the client is rendered before the DOM is updated. This means the client-side and server-side DOM will match.
JS Tip:
useEffect
is a React hook that lets you issue side effects - such as updating the DOM - after the page or component is mounted. The above example can be refactored:
const [isUserLoggedIn, setIsUserLoggedIn] = useState(false)
const user = Cookies.get('jwt')
useEffect(() => {
setIsUserLoggedIn(!!user?.id)
}, [user])
if (isUserLoggedIn) {
return <span>User logged in!</span>
}
return <span>User not logged in!</span>
The error goes away!