Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › hydration

Why Browser-Only Code Fails During Server Rendering—and How to Fix It

By Freedom251 Team · · 12 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Browser-only code fails during server-side rendering for two different reasons that often appear together. A server exception such as window is not defined means code tried to use a browser global where no browser exists. A hydration mismatch means the server generated one HTML result but the browser’s first React render generated another. The fixes overlap, but they are not interchangeable.

The reliable approach is to keep the server render and the initial client render deterministic, then read browser-only state after hydration or isolate the feature behind a client-only boundary when it cannot render meaningfully on the server. In Next.js, a file marked 'use client' can still be prerendered, so that directive alone does not make render-time access to window, document, or localStorage safe.

As an Amazon Associate I earn from qualifying purchases.

What is actually failing?

Server-side rendering (SSR) runs component code in a server runtime such as Node.js or an edge runtime. That environment can create HTML, but it does not provide the browser’s window, document, DOM nodes, or Web Storage APIs such as localStorage. Any code that touches those globals while the server is evaluating a module or rendering a component can throw before a response is produced.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Hydration is a separate stage. After the browser receives server-generated HTML, React renders the tree on the client and attaches event handlers and state to that existing markup. React expects the server output and the browser’s initial render to describe the same structure and text. If a browser-only lookup changes the result immediately, React can report that the text or markup does not match.

#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter | 4Pack Type C Female to A Male Cable Converter for Apple Duo 17 16 15 14 Plus,iWatch Watch 12 11 10,iPad 9 8 Air,Airpods 5 4
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

Think of the two failures as different questions:

  • Server exception: Did code access a browser API in an environment that has no browser?
  • Hydration mismatch: Did the server and the first client render produce different output?

A change can fix one without fixing the other. For example, wrapping a read in typeof window !== 'undefined' can prevent a server exception while still producing different server and client markup.

Why a client-designated component can still fail on the server

Next.js separates Server Components and Client Components, but a Client Component is not necessarily rendered exclusively in a browser. In the normal rendering pipeline, Next.js can prerender the component to HTML and then hydrate it in the browser. The client designation controls where the component’s interactive code and state are allowed to run; it does not guarantee that every expression in its render function runs only after a browser exists.

This is unsafe when the lookup occurs during rendering:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
'use client'

export default function Greeting() {
  const name = window.localStorage.getItem('name')
  return <p>Hello, {name}</p>
}

The server reaches window while producing the initial HTML and throws. Moving the same read into a lazy state initializer does not automatically solve the problem either, because the initializer can run during the server render:

const [name] = useState(() => window.localStorage.getItem('name'))

Module-level code has the same hazard. An imported library may read window as soon as the module is evaluated, before your component function runs. A component that looks harmless can therefore fail because one of its dependencies assumes a browser. Search both your own render path and imported packages when the stack trace points near an innocent-looking import.

How hydration mismatches are created

A browser-presence branch can change the first output

This pattern avoids an exception but creates two possible outputs:

export default function Status() {
  const text = typeof window === 'undefined' ? 'Loading' : 'Ready'
  return <p>{text}</p>
}

The server emits “Loading.” During hydration, the browser evaluates the same component with window available and immediately emits “Ready.” React now has existing server HTML containing one value and a first client tree containing another. The conditional itself is not inherently wrong; using environment detection to choose different markup during the initial render is what creates the split.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter | USB C Dongle with 90W Max Power Delivery, 3×5Gbps USB A Data Ports, for MacBook, Dell, and More (Charger Not Included)
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.

Storage, time, randomness, and other nondeterministic values

A value from localStorage, a current-time call such as Date(), a random number, or a browser-specific measurement can differ between server and browser. The same mismatch can occur even without browser APIs when data changes between the server response and hydration.

Next.js identifies several additional causes worth checking before blaming storage: invalid HTML nesting, browser extensions that modify the document, incorrect CSS-in-JS configuration, and an edge or CDN layer that rewrites the HTML response. A valid fix starts by comparing the actual server markup with the first client render instead of treating every warning as a storage problem.

A disciplined debugging sequence

  1. Classify the symptom. Record whether the request fails with a thrown exception such as window is not defined, or whether the page loads and the console reports different server and client content. An exception requires removing browser access from server execution; a mismatch requires making the initial outputs equivalent.
  2. Find every browser access in the render path. Search component bodies, state initializers, default property expressions, and module top-level code for window, document, navigator, media-query reads, DOM measurement, and storage calls. Inspect imported dependencies as well as local files. A library can execute browser code during import even when your component only calls a normal-looking function.
  3. Capture both versions of the output. Inspect the HTML returned by the server and compare it with what the component would return during the browser’s first render, before effects run. Look for changed text, missing elements, reordered children, different attributes, and conditional branches based on environment, time, or storage.
  4. Check non-browser causes. Validate HTML nesting, temporarily disable extensions that inject markup, review CSS-in-JS server configuration, and check whether an edge or CDN transformation is altering the response. Also look for time-dependent expressions that run independently on the server and client.
  5. Choose a boundary deliberately. Decide whether the UI can show a useful, stable fallback until hydration. If it can, defer the browser read to an effect. If it cannot render meaningfully without a browser, isolate that widget and disable SSR for that component where the framework supports it.
  6. Verify both stages. Test a server request and a real hydrated browser session. Confirm that the initial markup agrees, then check the delayed state transition, event handlers, and error paths. A successful build does not prove that the first client render matches the server.

Fix 1: keep the first render stable and read browser state in an effect

Use this option when the server can render a sensible fallback and the browser-specific value may arrive after hydration. React effects run only in the browser, so a read placed in useEffect cannot execute during SSR.

'use client'

import { useEffect, useState } from 'react'

export default function ThemeLabel() {
  const [theme, setTheme] = useState('light')

  useEffect(() => {
    const saved = window.localStorage.getItem('theme')
    if (saved === 'light' || saved === 'dark') {
      setTheme(saved)
    }
  }, [])

  return <p data-theme={theme}>Theme: {theme}</p>
}

Both the server and the initial browser render use light. After hydration, the effect reads storage and updates the state if a valid saved value exists. This satisfies React’s matching-output requirement while still allowing a browser preference to take effect.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The trade-off is visible: users whose saved value differs from the fallback see a transition after hydration. Choose a fallback that is safe, accessible, and visually acceptable. For a larger interface, render a neutral shell or skeleton rather than allowing the server to guess a personalized value that only the browser knows.

Conditionally rendering from localStorage without a mismatch

A common question is how to show a component only when a key exists in localStorage. Do not make the presence check decide the initial markup. Start with a shared state such as null, render the same placeholder on the server and first client pass, and perform the lookup in an effect:

'use client'

import { useEffect, useState } from 'react'

export default function SavedPanel() {
  const [hasSavedItem, setHasSavedItem] = useState(null)

  useEffect(() => {
    setHasSavedItem(window.localStorage.getItem('saved-item') !== null)
  }, [])

  if (hasSavedItem === null) {
    return <div aria-busy='true'>Checking saved items…</div>
  }

  return hasSavedItem
    ? <SavedItem />
    : <EmptyState />
}

The placeholder is deterministic. Only after hydration does the component choose between the saved and empty states. If the placeholder would be confusing or too costly, consider supplying the value from a server-readable request input instead of discovering it in the browser.

Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter | 85W Max PD, 2 USB 3.0 & 1 USBC Data Ports, SD/TF Card Reader, for Laptops/Mac and Other Type C Devices (Charger Not Included)
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.

Guard browser APIs inside effects as well

An effect is client-only in React, but defensive checks can still be useful when code is shared with unusual runtimes, tests, or rendering tools:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
useEffect(() => {
  if (typeof window === 'undefined') return
  const value = window.localStorage.getItem('key')
  // update state from value
}, [])

The guard is not a substitute for stable initial output. It protects the effect’s execution environment; it does not make a browser-dependent expression in the render function safe.

Fix 2: isolate a truly browser-only component and disable SSR

Some widgets cannot provide useful server output: a map that needs a DOM canvas, a chart library that measures layout immediately, or a third-party package that touches browser globals during import. In Next.js, a dynamic import with ssr: false can create a local browser-only boundary for that component:

import dynamic from 'next/dynamic'

const BrowserWidget = dynamic(() => import('./BrowserWidget'), {
  ssr: false,
})

export default function Page() {
  return <BrowserWidget />
}

The component is not prerendered on the server, so its browser assumptions do not execute there. Provide an explicit loading state if the surrounding page needs a stable layout.

This is a targeted trade-off, not a universal repair. The isolated component contributes no server-rendered content, cannot improve the initial HTML for search or non-JavaScript clients, and may appear later than the rest of the page. Keep the boundary as small as possible so the rest of the route retains SSR.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use the syntax and placement supported by your Next.js version. Framework boundaries are version-sensitive; an option available in one router or release may have different restrictions in another. The underlying decision remains the same: opt out of SSR only for code that genuinely cannot render outside a browser.

Fix 3: provide the value to the server when it must affect initial HTML

Deferring a value is not always acceptable. A language choice, consent state, theme, or request-specific feature flag may need to determine the HTML sent to the user. In that case, do not let the browser discover the value only after hydration. Obtain it from an input the server can read for that request, using the framework’s supported request APIs, and pass the result into the rendering tree.

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray | Data transfer & Fast charging for MacBook, iPhone 17/16/15, iPad, Samsung Galaxy, Apple Watch
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

The value might come from a cookie, a request header, a route segment, or data loaded on the server. The exact API differs between frameworks and Next.js versions, but the invariant is stable: the server and the first client render must receive equivalent initial data.

// Server-side layer (conceptual)
const initialTheme = readThemeFromRequest()
return <ThemeLabel initialTheme={initialTheme} />

// Client component
'use client'

export default function ThemeLabel({ initialTheme }) {
  const [theme, setTheme] = useState(initialTheme)
  // Browser-only synchronization can happen in an effect.
  return <p data-theme={theme}>Theme: {theme}</p>
}

Treat browser storage as user-controlled input. It is suitable for preferences, not authentication or authorization. Security-sensitive decisions must be based on trusted server-side validation, even when the interface also reads a browser value for convenience.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix 4: use suppressHydrationWarning only for a known, narrow difference

Next.js exposes suppressHydrationWarning as an escape hatch for unavoidable differences, such as a text value that is intentionally different between server and browser. It suppresses a warning at one level; it does not make the two renders equivalent, and React will not patch mismatched text content covered by that suppression.

<time suppressHydrationWarning>{displayValue}</time>

Use it only when the differing output is understood, isolated, and harmless. Do not apply it to a large container to hide an unknown mismatch. If a component’s structure, event targets, or important content differs, fix the data flow or move the component behind an appropriate client-only boundary instead.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Framework-specific lifecycle rules

Next.js and React

In React, effects are client-only, which makes them the normal place for post-hydration reads of storage, media queries, DOM measurements, and other browser APIs. Keep render functions pure with respect to the environment: given the same props and initial state, they should return the same result on the server and during the first client render.

In Next.js, remember that a Client Component may be prerendered. A 'use client' directive enables client features such as state and event handlers, but it does not authorize direct browser-global access during rendering. Use a stable initial state, a server-provided value, or a narrowly scoped no-SSR import according to the component’s needs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Nuxt 2

The cited Nuxt guidance is specifically for Nuxt 2. It places window and document access in the beforeMount or mounted lifecycle hooks and describes client-only handling with process.client. It also warns that divergent server and client output can produce a NodeMismatch.

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display | 85W Max PD, 1 Powered USB-C 5Gbps & 2 Powered USB-A 3.0 5Gbps Data Ports, for MacBook Pro, MacBook Air, Dell and More (Black)
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.

Do not copy Nuxt 2 APIs blindly into newer Nuxt releases. Check the documentation for the exact Nuxt version in use and apply its current client-only plugin or component conventions. The general rule still holds: browser code belongs in a client lifecycle or a client-only boundary, and the initial server and browser output must agree.

Choosing among the remedies

Remedy Stable server fallback possible? Value in initial HTML? Post-hydration change SSR cost Best use
Read in an effect Yes No; value arrives after hydration Usually yes Preserves SSR Preferences, storage flags, media queries, and DOM-dependent enhancements
Pass a server-readable input Yes, with the input Yes Optional synchronization later Preserves SSR Values that must determine the first HTML
Dynamic import with ssr: false Only a loading shell No component HTML from SSR Component appears when loaded Gives up SSR for that component Widgets or libraries that cannot execute meaningfully on the server
suppressHydrationWarning Not a rendering strategy Deliberately differs React does not repair suppressed text Does not remove server work A known, unavoidable, shallow text difference

Use the least disruptive option that satisfies the requirement. If a neutral fallback works, an effect generally keeps the most server-rendered content. If the initial value is important to the response, make it server-readable. If no server representation is meaningful, isolate only that feature rather than disabling SSR for an entire route. Warning suppression belongs at the end of the decision process, not at the beginning.

Common fixes that do not solve the underlying problem

Only adding typeof window in render

This can stop a reference error while leaving two different branches for the server and browser. Replace the branch with shared initial output and a post-hydration update, or provide the same data to both environments.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reading storage in a state initializer

Lazy initializers run during rendering and may execute on the server. Initialize with a deterministic fallback, then read storage in an effect unless the component is completely excluded from SSR.

Marking a parent as client-only without checking imports

A client boundary does not prevent every imported module from being evaluated during prerendering. If a dependency touches browser globals at module scope, isolate that dependency behind a supported dynamic import or use a package entry point designed for SSR.

Suppressing the warning globally

A broad suppression hides evidence and can leave stale text, incorrect event targets, or inaccessible content. Keep suppression on the smallest known text node, and only after confirming that the difference is intentional.

Assuming every hydration warning comes from browser APIs

Inspect invalid nesting, dates, random values, extension-injected nodes, CSS-in-JS setup, and response mutation at the edge. Fixing storage code will not correct a malformed HTML tree or a CDN rewrite.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Testing the corrected implementation

  • Render the route in the server environment with no browser globals and confirm that no import or render path throws.
  • Inspect the returned HTML and record the fallback text, attributes, and element structure.
  • Load the page in a clean browser profile and observe the DOM before effects update state.
  • Confirm that the first client output matches the server HTML and that the console has no hydration warning.
  • Wait for effects and verify the browser-specific value, loading transition, and event handlers.
  • Test missing, malformed, and unexpected storage values; browser storage can be cleared, blocked, or changed between visits.
  • Exercise the page through the framework’s production build, not only a development server. Import timing, streaming, and edge execution can expose issues that a local setup hides.

What to remember

The server cannot provide window, document, the DOM, or localStorage. Direct use during server execution can therefore throw. Hydration adds a second requirement: the browser’s first render must match the HTML already sent by the server.

For most preference and storage cases, render a deterministic fallback and read the browser value in an effect. When a value must shape the initial response, pass it from a server-readable request input. When a widget fundamentally requires a browser, isolate it behind a framework-supported client-only boundary and accept the loss of server-rendered output for that widget. Treat warning suppression as a narrowly scoped exception, not as a repair.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

hydrationJavaScriptlocalStorageNext.jsReactserver-side renderingSSR

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.