Understanding Hydration Error

Introduction
Hydration is the process of taking the HTML (static content) generated by the server and adding interactivity to it on the client side, making the static content dynamic and responsive to user interactions. In this article, we will talk about the two main rendering strategies in modern web development, hydration errors, and how to avoid them.
Server-Side Rendering (SSR) and Client-Side Rendering (CSR)
To understand hydration errors, we need to explore the two main key rendering strategies in modern web development.
Server-Side Rendering (SSR)
In SSR, the server generates the whole HTML page on each request, which includes the initial contents and necessary data. When a user requests a page, the process looks like this:
The server collects the necessary data (e.g., from a database or an API)
The server loads or renders the HTML template with the data
The server sends the complete HTML page to the browser.
From here, the browser can take over the rendered HTML and add JavaScript interactivity.
Client-Side Rendering (CSR)
In CSR, the server sends a basic HTML template to the browser. Then JavaScript takes over from there by loading dynamic data and adding interactivity. When a user requests a page, the process looks like this:
The server sends a very minimal HTML template
The browser executes JavaScript to fetch data and dynamically build the contents.
The JavaScript-generated content is displayed on the browser.
Hydration Error
A hydration error occurs when the server-generated HTML does not match the HTML generated by the JavaScript on the browser. Hydration error is common in SSR applications such as Nuxt.js and Next.js, where the initial HTML is rendered on the server, and the client-side JavaScript may not align perfectly. This can cause a hydration error.
Common Causes of Hydration Errors
Hydration errors are usually caused by the following factors:
State Mismatch
This occurs when the server-generated HTML has different data than what the client-side JavaScript expects.
Problem:
// Server-side rendering (SSR)
const data = { name: 'John' };
const html = `<div>Hello, ${data.name}!</div>`;
// Client-side rendering
fetch('/api/data')
.then(response => response.json())
.then(data => {
document.getElementById('name').innerText = `Hello, ${data.name}!`;
});
The problem above is that the server-generated HTML contains hello John
, but the client-side JavaScript expects to render an element with the id
value of name
. But the server-generated HTML does not have an element with that ‘id’.
Fix:
// Server-side rendering (SSR)
fetch('/api/data')
.then(response => response.json())
.then(data => {
const html = `<div id="name">Hello, ${data.name}!</div>`;
// send html to client
});
In the solution above, the server fetches the data and includes it in the initial HTML. The client-side JavaScript also fetches the data and updates the HTML. This way, the initial HTML rendered by the server matches the HTML rendered by the client-side JavaScript.
Random Content or Unpredictable Contents
This occurs when components rely on values that change between the server and client render.
Problem:
// Server-side rendering (SSR)
const randomNumber = Math.random();
const html = `<div>Random number: ${randomNumber}</div>`;
// Client-side rendering
document.getElementById('random').innerText = `Random number: ${Math.random()}`;
The server-generated HTML contains a random number, and the client-side JavaScript generates a random number, which will cause a mismatch.
Fix:
// Server-side rendering (SSR)
const html = `<div id="random"></div>`;
// Client-side rendering
document.getElementById('random').innerText = `Random number: ${Math.random()}`;
The solution above shows you just need to send the HTML template and generate the number on the client-side only.
Conditional rendering
If components are rendered differently on the client and on the server based on specific conditions to properties like the window size, mismatches can occur.
Problem:
// Server-side rendering (SSR)
const html = `<div id="menu">Desktop menu</div>`; // assumes desktop device
// Client-side rendering
if (window.innerWidth < 768) {
document.getElementById('menu').innerHTML = '<div>Mobile menu</div>';
} else {
document.getElementById('menu').innerHTML = '<div>Desktop menu</div>';
}
The server-generated HTML assumes a desktop device, but the client-side JavaScript checks the window size and renders a different menu.
Fix:
// Server-side rendering (SSR)
const html = `<div id="menu"></div>`;
// Client-side rendering
if (window.innerWidth < 768) {
document.getElementById('menu').innerHTML = '<div>Mobile menu</div>';
} else {
document.getElementById('menu').innerHTML = '<div>Desktop menu</div>';
}
The recommendation here is to use client-side rendering only for conditional content.
Client-Side Only Code
These are scripts, tags, event listeners, or animations that are only meant to run on the client-side; they won’t be properly handled in the server-side rendering.
Problem:
// Server-side rendering (SSR)
const html = `<button>Click me!</button>`;
// Client-side rendering
document.querySelector('button').addEventListener('click', () => {
console.log('Button clicked!');
});
The problem above is the server-generated HTML is static, while the event listener is added dynamically on the client-side using JavaScript.
FIX:
// Client-side rendering
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('button').addEventListener('click', () => {
console.log('Button clicked!');
});
});
What we are doing here is that the code waits for the DOM to be fully loaded before adding a click event listener to the button, ensuring the element exists and avoiding errors.
The concepts of state mismatches, random content, conditional rendering, and client-side-only code are foundational in both vanilla JavaScript and frameworks like Next.js and Nuxt.js. These frameworks handle SSR (Server-Side Rendering) and CSR (Client-Side Rendering) by providing tools to synchronize data and manage dynamic content. For instance, Next.js uses getServerSideProps and Nuxt.js uses asyncData to ensure consistent data across the server and client. They also handle random values and conditional rendering by ensuring dynamic content is generated client-side, avoiding mismatches. Next.js uses next/dynamic for client-only components, and Nuxt.js uses client-only components to manage client-specific code. These techniques simplify hydration by ensuring server and client renderings stay in sync, helping avoid hydration errors.
Conclusion
In this article, we've explored the concept of hydration. We discussed the two key rendering strategies, SSR and CSR, and how hydration errors typically occur when there’s a mismatch between the HTML sent by the server and the HTML generated on the client-side.
We also highlighted the common causes of hydration errors, including state mismatches, random content, conditional rendering, and client-side-only code. For each, we provided simple examples and fixes to ensure consistent content across server and client renders.
Finally, we saw how modern frameworks like Next.js and Nuxt.js provide powerful tools like getServerSideProps
and asyncData
to synchronize data, handle dynamic content, and prevent mismatches. These frameworks simplify the hydration process by ensuring that server and client renders align, ultimately preventing hydration errors.
By understanding these basic principles and applying the right techniques, developers can avoid hydration issues and create seamless, interactive web applications that function smoothly across both server-side and client-side rendering.
Subscribe to my newsletter
Read articles from Abimbola Adedotun Samuel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Abimbola Adedotun Samuel
Abimbola Adedotun Samuel
I am a front-end developer, tech explorer, loves public speaking, documenting my journey/challenges.