How to Test React Hydration
React hydration bridges the gap between server-rendered HTML and client-side interactivity, but mismatches between the server and client markup can cause UI bugs and performance regressions. This article explains how to effectively test React hydration and catch mismatch errors using browser developer tools, automated end-to-end testing frameworks, and unit testing configurations.
1. Manual Testing with Browser Developer Tools
The fastest way to test for hydration issues during development is to inspect the browser console. When React detects a difference between the server-rendered HTML and the client-rendered virtual DOM, it logs a warning in development mode.
To test this manually: 1. Run your React application in development
mode. 2. Open your browser’s Developer Tools (F12) and navigate to the
Console tab. 3. Look for warnings containing phrases
like: * Warning: Did not expect server HTML to contain a...
* Warning: Text content did not match... *
An error occurred during hydration...
If these warnings appear, it means your server-rendered HTML does not
match what the client expected on the initial render. Common culprits
include using browser-only globals (like window or
localStorage) during the initial render or using dynamic
data like new Date() or Math.random().
2. Automated E2E Testing with Playwright
Manual testing is prone to human error, making automated testing essential. Because hydration is a browser-only process, end-to-end (E2E) testing tools like Playwright are ideal for catching hydration failures in CI/CD pipelines.
You can configure Playwright to listen for console warnings and fail the test if a hydration warning is detected.
import { test, expect } from '@playwright/test';
test('should load the page without hydration errors', async ({ page }) => {
const warnings = [];
// Listen for console warnings and errors
page.on('console', (msg) => {
if (msg.type() === 'warning' || msg.type() === 'error') {
if (msg.text().includes('hydration') || msg.text().includes('did not match')) {
warnings.push(msg.text());
}
}
});
await page.goto('http://localhost:3000');
// Verify that no hydration warnings were captured
expect(warnings).toEqual([]);
});3. Unit Testing with React Testing Library
You can test how your components behave during hydration at the unit
level using React Testing Library and react-dom/client.
Instead of using standard rendering, you can mock the SSR output and
call React’s hydrateRoot to test the hydration process
directly.
import { screen } from '@testing-library/react';
import { hydrateRoot } from 'react-dom/client';
import React from 'react';
test('hydrates correctly without errors', () => {
const container = document.createElement('div');
// 1. Simulate Server-Side Rendered HTML
container.innerHTML = '<button id="btn">Click me</button>';
document.body.appendChild(container);
const consoleWarnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// 2. Hydrate the container with the client component
const App = () => <button id="btn">Click me</button>;
hydrateRoot(container, <App />);
// 3. Assert no hydration warnings were triggered
expect(consoleWarnSpy).not.toHaveBeenCalled();
consoleWarnSpy.mockRestore();
document.body.removeChild(container);
});4. Monitoring Hydration Failures in Production
Sometimes hydration errors only occur in production under specific
conditions. You can capture these errors programmatically by leveraging
the onRecoverableError callback inside the
hydrateRoot configuration. This allows you to log hydration
errors directly to your error-tracking service (such as Sentry or
LogRocket).
import { hydrateRoot } from 'react-dom/client';
import App from './App';
hydrateRoot(
document.getElementById('root'),
<App />,
{
onRecoverableError: (error, errorInfo) => {
console.error('Hydration error captured:', error);
// Send error telemetry here
}
}
);