The Next.js server is complaining because it found HTML attributes in the server-rendered output that it didn’t expect and can’t hydrate properly.
Common Causes and Fixes:
-
aria-*attributes on non-interactive elements:- Diagnosis: Inspect the HTML output of your server-rendered pages. Look for
aria-*attributes (likearia-label,aria-hidden,aria-expanded) attached directly to elements like<div>or<span>that are not intended to be interactive by default, and where these attributes might be confusing the client-side hydration. - Fix:
- Option A (Recommended): Associate the
aria-*attribute with a genuinely interactive element. For example, if you havearia-label="Close"on adivthat acts as a close button, move it to the actual<button>element that triggers the close action. - Option B (If Option A is not feasible): If you must have the
aria-*attribute on a non-interactive element for styling or structural reasons, you might need to conditionally render it or use a specific role that implies interactivity. However, this often indicates an accessibility pattern that could be improved. - Example Fix (Option A):
<!-- Before (problematic) --> <div aria-label="Close" onClick={handleClose}>...</div> <!-- After (correct) --> <button aria-label="Close" onClick={handleClose}>...</button>
- Option A (Recommended): Associate the
- Why it works: Accessibility attributes are designed to convey semantic meaning to assistive technologies. When Next.js hydrates, it expects these attributes to be on elements where they make sense semantically and functionally. Placing them on inert elements can lead to hydration mismatches because the client-side JavaScript might not be expecting to process them there, or they might be misinterpreted.
- Diagnosis: Inspect the HTML output of your server-rendered pages. Look for
-
Custom
data-*attributes that are not handled correctly:- Diagnosis: Search your server-rendered HTML for
data-*attributes. If you have many customdata-*attributes, especially those being dynamically generated or manipulated, they might be the culprit. - Fix: Ensure that any custom
data-*attributes you are using are intended for client-side JavaScript manipulation and are not interfering with Next.js’s server-side rendering and hydration process. If adata-*attribute is being used purely for styling via CSS, and Next.js is warning about it, consider if it’s truly necessary or if there’s a cleaner way to achieve the styling. Sometimes, simply removing unnecessarydata-*attributes resolves the issue.- Example Fix: If you have
<div data-custom-state="active">and this is causing issues, anddata-custom-stateis only used by your own client-side JS or for styling that could be achieved otherwise, remove it.
<!-- Before (potentially problematic) --> <div data-custom-state="active" className="some-class">...</div> <!-- After (if data-custom-state is not essential for hydration/semantics) --> <div className="some-class">...</div> - Example Fix: If you have
- Why it works: While
data-*attributes are generally safe, an excessive number or specific patterns might be interpreted by Next.js’s reconciliation process as something that should have been handled server-side, leading to a mismatch.
- Diagnosis: Search your server-rendered HTML for
-
Directly injecting HTML with unexpected attributes into
dangerouslySetInnerHTML:- Diagnosis: If you’re using
dangerouslySetInnerHTMLto render HTML snippets, carefully examine the HTML string being passed to it. Look for any attributes that are not standard HTML attributes or are being added dynamically in a way that might be problematic. - Fix: Sanitize or modify the HTML string before passing it to
dangerouslySetInnerHTML. Remove any custom or unexpected attributes that might be causing the hydration warning. If you’re injecting third-party HTML, ensure it’s clean and adheres to standard HTML practices.- Example Fix:
// Assume rawHtml contains something like '<span data-id="123" style="color: red;">Text</span>' const cleanHtml = rawHtml.replace(/data-id="[^"]*"/g, ''); // Remove data-id attribute return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
- Example Fix:
- Why it works:
dangerouslySetInnerHTMLbypasses React’s normal attribute handling. If the injected HTML contains attributes that Next.js’s server-side rendering expects to control or hydrate, but they are present in a raw, unmanaged form, it will cause a mismatch.
- Diagnosis: If you’re using
-
Third-party component issues:
- Diagnosis: If the warning appears after integrating a new third-party component (e.g., a UI library component, a custom widget), inspect that component’s rendered output. Third-party components might inadvertently include non-standard attributes or attributes in unexpected places.
- Fix:
- Option A: Check the third-party component’s documentation for known issues or specific props related to server-side rendering or hydration. Update the component to the latest version.
- Option B: If possible, configure the component to omit certain attributes during server rendering, or wrap its output in a way that Next.js can handle.
- Option C: Fork the component and remove the problematic attribute if it’s not essential.
- Why it works: React (and by extension Next.js) has a specific set of known attributes it understands for DOM manipulation and event handling. Third-party code might introduce proprietary attributes that React doesn’t recognize during hydration, leading to the warning.
-
Server-side logic inadvertently adding attributes:
- Diagnosis: If you have custom server-side logic (e.g., in
getServerSidePropsor custom API routes that render HTML) that generates HTML or modifies props before they are passed to the component, review that logic. It might be adding attributes to elements that cause conflicts. - Fix: Ensure that any attributes added by server-side logic are standard HTML attributes or are explicitly handled by your client-side React components. Remove or adjust any custom attributes that are not meant for the client-side hydration.
- Example Fix: If your
getServerSidePropsis adding adata-server-generated-idattribute to adivthat your client component doesn’t expect or handle, remove it from the server-side generation.
- Example Fix: If your
- Why it works: Next.js’s server rendering generates an initial HTML payload. When the client-side JavaScript loads, it compares this initial HTML with what it expects to render based on the component’s state and props. If server-side logic injects unexpected attributes, this comparison fails, and the warning is triggered.
- Diagnosis: If you have custom server-side logic (e.g., in
-
Incorrectly handling conditional rendering of attributes:
- Diagnosis: If you’re conditionally adding attributes based on certain states or props, there might be a subtle bug where the attribute is present on the server but not on the client (or vice-versa) during the initial render, even if it’s supposed to be there.
- Fix: Ensure your conditional logic for adding attributes is consistent between server and client. Use
process.browserchecks if necessary to ensure attributes are only applied in the correct environment, or ensure the condition that determines the attribute’s presence is stable and evaluated identically on both sides.- Example Fix:
function MyComponent({ isActive }) { const extraProps = {}; if (isActive) { extraProps.className = 'active'; // If you were adding a problematic attribute conditionally: // extraProps['data-state'] = 'active'; } // Ensure 'data-state' is only added if it's a recognized attribute // or if its presence is managed correctly across SSR and CSR. return <div {...extraProps}>Content</div>; }
- Example Fix:
- Why it works: Hydration is about matching the server’s HTML structure and attributes with the client’s virtual DOM. If conditional logic leads to a different set of attributes being present on the server compared to what the client expects initially, it breaks the match.
The next error you’ll likely encounter after fixing this warning is a "Hydration mismatch" error, which is the underlying cause.