Back to Blog
Debugging

Solving Common Next.js Hydration Errors

BiBimlesh Kumar
March 10, 2024
10 min read
Solving Common Next.js Hydration Errors

Understanding Next.js Hydration

Hydration is a critical process in Next.js applications where the server-rendered HTML is made interactive by attaching event listeners and reconciling the DOM with the React component tree. When this process encounters inconsistencies between the server-rendered HTML and what React expects to render on the client, hydration errors occur.

Common Hydration Errors

Let's explore the most common hydration errors in Next.js applications and how to solve them:

1. Text Content Does Not Match Server-Rendered HTML

This is one of the most frequent hydration errors, occurring when the text content rendered on the server differs from what's rendered on the client.

Example Error:

Warning: Text content did not match. Server: "Hello World" Client: "Hello User"

Common Causes:

  • Using browser-only APIs like window or localStorage during rendering
  • Different date or time formatting between server and client
  • Random values or UUIDs generated during rendering

Solutions:


// INCORRECT - Will cause hydration errors
function Greeting() {
  const [name, setName] = useState("User");
  
  useEffect(() => {
    // This runs after hydration, causing a mismatch
    setName(localStorage.getItem("username") || "User");
  }, []);
  
  return 

Hello {name}

; } // CORRECT - Prevents hydration errors function Greeting() { const [name, setName] = useState("World"); // Default value for both server and client const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); setName(localStorage.getItem("username") || "User"); }, []); return

Hello {isClient ? name : "World"}

; }

2. Attribute Values Do Not Match

This occurs when HTML attributes or React props differ between server and client rendering.

Example Error:

Warning: Prop 'style' did not match. Server: "color:blue" Client: "color:red"

Common Causes:

  • Dynamic styles based on browser-only information
  • Conditional props based on client-side state
  • Different class names generated on server vs. client

Solutions:


// INCORRECT - Will cause hydration errors
function ColoredText() {
  const [color, setColor] = useState("blue");
  
  useEffect(() => {
    // This runs after hydration, causing a mismatch
    setColor(window.matchMedia("(prefers-color-scheme: dark)").matches ? "white" : "black");
  }, []);
  
  return 

Colored text

; } // CORRECT - Prevents hydration errors function ColoredText() { const [color, setColor] = useState("blue"); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); setColor(window.matchMedia("(prefers-color-scheme: dark)").matches ? "white" : "black"); }, []); // On first render, return the server version if (!mounted) { return

Colored text

; } // After hydration, return the client version return

Colored text

; }

3. Extra or Missing DOM Nodes

This error happens when the structure of the DOM differs between server and client rendering.

Example Error:

Warning: Expected server HTML to contain a matching <div> in <main>.

Common Causes:

  • Conditional rendering based on client-side information
  • Using portals or third-party components that modify the DOM
  • Dynamic lists with keys that change between server and client

Solutions:


// INCORRECT - Will cause hydration errors
function MobileMenu() {
  const [isMobile, setIsMobile] = useState(false);
  
  useEffect(() => {
    setIsMobile(window.innerWidth < 768);
  }, []);
  
  return (
    
  );
}

// CORRECT - Prevents hydration errors
function MobileMenu() {
  const [isMobile, setIsMobile] = useState(false);
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
    setIsMobile(window.innerWidth < 768);
  }, []);
  
  // On first render, always use DesktopNav for both server and client
  if (!mounted) {
    return (
      
    );
  }
  
  // After hydration, render based on screen size
  return (
    
  );
}
      

Advanced Hydration Error Solutions

1. Using Dynamic Imports with Client-Only Components

For components that rely heavily on browser APIs, you can use dynamic imports with the ssr: false option:


// components/BrowserOnlyComponent.js
import { useEffect, useState } from 'react';

export default function BrowserOnlyComponent() {
  const [browserInfo, setBrowserInfo] = useState(null);
  
  useEffect(() => {
    setBrowserInfo({
      userAgent: navigator.userAgent,
      language: navigator.language,
      screenWidth: window.innerWidth,
    });
  }, []);
  
  return (
    
{browserInfo && (
  • User Agent: {browserInfo.userAgent}
  • Language: {browserInfo.language}
  • Screen Width: {browserInfo.screenWidth}px
)}
); } // pages/index.js import dynamic from 'next/dynamic'; const BrowserOnlyComponent = dynamic( () => import('../components/BrowserOnlyComponent'), { ssr: false } ); export default function Home() { return (

My App

); }

2. Using the useId Hook for Stable IDs

React's useId hook generates stable IDs that match between server and client:


import { useId } from 'react';

function FormField({ label }) {
  const id = useId();
  
  return (
    
); }

3. Using suppressHydrationWarning

For cases where differences are expected and harmless, you can use the suppressHydrationWarning prop:


function CurrentTime() {
  return (
    
Current time: {new Date().toLocaleTimeString()}
); }

Note: Use this sparingly and only when the mismatch is expected and doesn't affect functionality.

Debugging Hydration Errors

1. Using React Developer Tools

React Developer Tools can help identify components causing hydration issues:

  • Install the React Developer Tools browser extension
  • Open your app and check the console for hydration warnings
  • Use the Components tab to inspect the problematic components

2. Using the React Strict Mode

Enable Strict Mode in your Next.js application to catch potential issues:


// next.config.js
module.exports = {
  reactStrictMode: true,
}
      

3. Creating a Hydration Boundary

Isolate problematic components with a hydration boundary:


function HydrationBoundary({ children }) {
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
  }, []);
  
  if (!mounted) {
    return 
Loading...
; } return <>{children}; } // Usage function MyPage() { return (
{/* Server and client rendered */} {/* Only client rendered */}
{/* Server and client rendered */}
); }

Next.js App Router Specific Solutions

1. Using Client Components

In the App Router, you can mark components as client-only using the 'use client' directive:


// components/ClientComponent.js
'use client'

import { useState, useEffect } from 'react';

export default function ClientComponent() {
  const [windowWidth, setWindowWidth] = useState(0);
  
  useEffect(() => {
    const handleResize = () => setWindowWidth(window.innerWidth);
    handleResize();
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return 
Window width: {windowWidth}px
; }

2. Using the cookies() Function

For cookie access in Server Components, use the cookies() function instead of document.cookie:


// app/page.js
import { cookies } from 'next/headers';

export default function Page() {
  const cookieStore = cookies();
  const theme = cookieStore.get('theme')?.value || 'light';
  
  return 
Current theme: {theme}
; }

3. Using Server-Only and Client-Only Packages

Use the server-only and client-only packages to prevent accidental usage in the wrong environment:


// Install the packages
// npm install server-only client-only

// In a server-only file
import 'server-only';

export async function getDataFromDatabase() {
  // Server-only code
}

// In a client-only file
import 'client-only';

export function useWindowSize() {
  // Client-only code
}
      

Best Practices to Prevent Hydration Errors

1. Avoid Direct DOM Manipulation

Let React handle the DOM to ensure consistency between server and client rendering.

2. Use Conditional Rendering with useEffect

Always defer browser-specific logic to useEffect and use conditional rendering to handle the pre-hydration state.

3. Be Careful with Third-Party Libraries

Some libraries may not be SSR-compatible. Check documentation or wrap them in dynamic imports with { ssr: false }.

4. Use Consistent Data Sources

Ensure data used for rendering comes from consistent sources on both server and client.

5. Test in Production Mode

Development mode may hide some hydration issues. Test your application in production mode before deploying.

Conclusion

Hydration errors can be frustrating, but with a proper understanding of how Next.js handles server-side rendering and client-side hydration, most issues can be resolved systematically. By following the patterns and best practices outlined in this article, you can ensure a smooth user experience with consistent rendering between server and client.

Remember that the key to avoiding hydration errors is to ensure that the initial render on the client matches exactly what was rendered on the server, and then apply client-specific changes only after hydration is complete.

Tags:
Next.js
Hydration
Debugging
React
Share:

Related Articles

Moving Beyond Create React App: Modern Alternatives
Frameworks

Moving Beyond Create React App: Modern Alternatives

With Create React App's deprecation, explore modern alternatives like Vite, Next.js, and Remix for your React projects.

Optimizing Image Delivery with ImageKit and Next.js
Integration

Optimizing Image Delivery with ImageKit and Next.js

Learn how to integrate ImageKit with Next.js for optimized image delivery, transformations, and improved performance.

Implementing Secure Payments with Razorpay in Next.js
Payments

Implementing Secure Payments with Razorpay in Next.js

A comprehensive guide to integrating Razorpay payment gateway in your Next.js applications with best security practices.

Subscribe to Our Newsletter

Get the latest articles and project management insights delivered to your inbox.