afterbuild/ops
ERR-HYD/Next.js · React 19
ERR-HYD
Hydration failed because the initial UI does not match what was rendered on the server. Text content did not match.

appears when:Right after page load in production; dev mode shows a softer warning or nothing at all.

Hydration failed because the initial UI does not match what was rendered on the server

The server rendered one string, the client rendered another. React bails out of hydration, the page flashes, interactivity breaks. Three root causes cover nearly every case and each has one real fix — not suppressHydrationWarning.

Last updated 17 April 2026 · 6 min read · By Hyder Shah
Direct answer

A Next.js hydration error in production means the server HTML and the first client render produced different output. The fix is to render a stable value on the server and update it inside useEffect after mount. Date.now(), localStorage, and window-dependent conditionals must move into useEffect or into a component loaded with dynamic(..., { ssr: false }).

Quick fix for Next.js hydration error production

app/components/ThemeToggle.tsx
tsx
01// app/components/ThemeToggle.tsx — stable SSR, real value after mount02"use client";03 04import { useEffect, useState } from "react";05 06export function ThemeToggle() {07  // SSR renders the same thing the first client pass will: "light"08  const [theme, setTheme] = useState<"light" | "dark">("light");09 10  useEffect(() => {11    // Read localStorage only after hydration — no mismatch possible12    const saved = window.localStorage.getItem("theme");13    if (saved === "dark" || saved === "light") setTheme(saved);14  }, []);15 16  return (17    <button18      suppressHydrationWarning19      onClick={() => {20        const next = theme === "light" ? "dark" : "light";21        setTheme(next);22        window.localStorage.setItem("theme", next);23      }}24    >25      {theme}26    </button>27  );28}
Render the stable default on the server, hydrate the real value in useEffect.

Deeper fixes when the quick fix fails

01 · Dynamic import with ssr: false for browser-only components

Chat widgets, charting libraries that depend on window, anything that reaches for DOM at module load — these should skip SSR entirely.

app/components/ChatWidget.tsx
tsx
01import dynamic from "next/dynamic";02 03const ChatWidget = dynamic(() => import("./chat-widget-impl"), {04  ssr: false,05  loading: () => null,06});07 08export default ChatWidget;
ssr: false → the component is skipped on the server entirely.

02 · Render stable initial values, update on mount

For timestamps and relative dates, render the ISO value on the server and compute the “3 seconds ago” label in useEffect.

app/components/TimeAgo.tsx
tsx
01"use client";02 03import { useEffect, useState } from "react";04import { formatRelative } from "date-fns";05 06export function TimeAgo({ iso }: { iso: string }) {07  const [label, setLabel] = useState(iso);08 09  useEffect(() => {10    setLabel(formatRelative(new Date(iso), new Date()));11  }, [iso]);12 13  return <time dateTime={iso}>{label}</time>;14}
Server renders ISO, client swaps to relative after mount.

03 · Load third-party scripts with next/script

Cookie banners, chat widgets, analytics — load them via next/script with strategy="lazyOnload" so they inject after hydration completes.

app/layout.tsx
tsx
01import Script from "next/script";02 03export default function RootLayout({04  children,05}: {06  children: React.ReactNode;07}) {08  return (09    <html>10      <body>11        {children}12        <Script13          src="https://widget.example.com/loader.js"14          strategy="lazyOnload"15        />16      </body>17    </html>18  );19}
lazyOnload waits until after hydration — no mismatch.

Why AI-built apps hit Next.js hydration error production

AI-generated React code does not distinguish between server and client contexts. The model writes a component that reads localStorage for a theme preference, and the same component later becomes a server component because you wrapped the page in a layout that made it one. localStorage does not exist on the server. Next.js renders the server version as empty or default, ships the HTML, and the client then tries to hydrate with a different value — hydration error. The model has no way of knowing the component later changed context.

The second common source is timestamp rendering. A product card that shows “posted 3 seconds ago” using Date.now() produces 3 seconds on the server and 4 seconds by the time the client mounts. React compares the text nodes, finds them different, throws. The fix is to render a stable initial value (the ISO timestamp) and compute the relative time in useEffect. The server still renders something sensible for SEO; the client replaces it on mount.

The third pattern is third-party script injection. Chatbots, cookie banners, and A/B testing tools mount DOM before React hydrates. The DOM now contains nodes the React tree does not know about. React panics and re-renders. The fix is either to load the script with next/script and a strategy="lazyOnload", or to mount the component inside useEffect so React never sees the injection at hydration time. Nearly every hydration error traces to one of these three shapes.

Next.js hydration error production by AI builder

How often each AI builder ships this error and the pattern that produces it.

AI builder × Next.js hydration error production
BuilderFrequencyPattern
LovableHighlocalStorage read during render; component later promoted to server component
Bolt.newHighDate.now() in a relative-time helper that renders on both sides
v0MediumConditional render on window width; server renders one layout, client renders another
CursorMediumThird-party script injected via inline <script> before React mounts
Claude CodeLowOccasionally uses Math.random() for an id; rare but happens

Related errors we fix

Stop Next.js hydration error production recurring in AI-built apps

Still stuck with Next.js hydration error production?

Emergency triage · $299 · 48h turnaround
We restore service and write the root-cause report.

When hydration fires on five or more routes or only reproduces for some users, a fixed-price engagement ships this week:

  • Hydration warning on multiple routes you cannot track down
  • Works locally, fires only in production — intermittent
  • Third-party scripts (cookie banner, chatbot) you cannot migrate to next/script
  • You need a Playwright repro to catch regressions
start the triage →

Next.js hydration error production questions

What does the Next.js hydration error production message actually mean?+
Next.js renders your page on the server into HTML, then the browser downloads the JavaScript and re-renders the same components. Hydration is the process of attaching React event handlers to that server-rendered HTML. If the client render produces different output than the server render, React cannot attach handlers cleanly and throws the Next.js hydration error in production. The UI may look correct but interactivity is broken and the console is red.
Which APIs cause the Next.js hydration error in production?+
Any API that returns a different value on the server than in the browser. Date.now() and new Date() because server and client clocks differ. Math.random() for the same reason. window, document, localStorage, and sessionStorage because they do not exist on the server. User-locale formatting because the server does not know the browser's locale. Third-party scripts that inject DOM before React hydrates. All of these need guarding with useEffect or dynamic imports.
Why does the Next.js hydration error only happen in production?+
In development, Next.js shows a verbose hydration warning and renders both outputs. In production, React bails out of hydration and re-renders the entire tree on the client, which looks like a flash of unstyled content and breaks interactivity on some elements. The bug was always there — production is just the first place where the consequences are visible to users. Run npm run build && npm start locally to see the same behavior.
Is suppressHydrationWarning a real fix for the Next.js hydration error in production?+
It is an escape hatch, not a fix. suppressHydrationWarning tells React to ignore the mismatch on a specific element. Use it for intentional differences — a timestamp that updates every second, a randomized id on a form — but do not use it to silence a real bug. The mismatch is still happening; you just hid the warning.
How long does a Next.js hydration error production fix take?+
Twenty minutes for a single component once you identify the offending API. One to two hours for an app-wide audit if hydration errors are showing up on multiple pages. Our Emergency Triage at $299 covers a hydration investigation in 48 hours when the error fires intermittently and you cannot reproduce it on command.
Next step

Ship the fix. Keep the fix.

Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.

About the author

Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.

Next.js hydration error production experts

If this problem keeps coming back, you probably need ongoing expertise in the underlying stack.

Sources