Server rendering makes React applications faster to load and easier to index, but it also introduces an important limitation: server code does not have access to browser APIs.
Objects such as window, document, localStorage, navigator, and browser-specific APIs only exist after the application reaches the browser.
This can become a problem when a component needs browser-only functionality while the rest of the application is rendered on the server.
React 19.3 introduces the browser() API for explicitly marking code that should run only in a browser environment. It provides a clearer way to separate browser-specific work from server rendering.
This article explains how browser() works, when it is useful, how it compares with common browser checks, and how to avoid server-rendering problems in production React applications.
Why Browser-Only Code Is a Problem
Consider a component that reads from localStorage:
function UserPreferences() {
const theme = localStorage.getItem("theme");
return <div>Current theme: {theme}</div>;
}
This works in a browser.
It can fail during server rendering because localStorage is not available on the server.
The same issue applies to:
window.innerWidth
or:
document.querySelector(...)
or:
navigator.language
A server-rendered React application needs to distinguish between code that can safely execute in both environments and code that requires a browser.
What Is React browser()?
React 19.3 provides browser() as an explicit mechanism for code that depends on browser-only capabilities.
The important concept is that browser-specific work should be isolated rather than relying on accidental runtime checks.
A simplified example is:
import { browser } from "react";
function Analytics() {
browser();
return <div>Analytics dashboard</div>;
}
The browser() call tells React that this component requires a browser environment.
This is especially relevant to server rendering because React can identify browser-dependent components instead of treating them as universally renderable code.
Browser-Only Components
A practical use case is a component that uses browser APIs.
For example:
import { browser } from "react";
function OnlineStatus() {
browser();
const online = navigator.onLine;
return (
<p>
Status: {online ? "Online" : "Offline"}
</p>
);
}
The component depends on navigator, which is a browser API.
Using an explicit browser boundary makes that dependency clear.
However, browser-only rendering does not mean that every browser-dependent component should automatically be moved out of server rendering. In many applications, it is better to render a stable server-safe fallback and enhance the component after hydration.
browser() vs typeof window
Before browser-aware React APIs, developers commonly wrote:
if (typeof window !== "undefined") {
// Browser-only code
}
This pattern is still useful in some situations, but it does not necessarily communicate the rendering intent as clearly.
Compare the two approaches.
Approach | Purpose | Main Concern |
|---|---|---|
| Runtime environment check | Easy to scatter throughout code |
| Run code after hydration | Not suitable when rendering itself depends on browser state |
Dynamic client-only loading | Prevent server rendering | Framework-specific |
| Explicit browser-only component boundary | Requires React version and environment support |
The important distinction is that an environment check answers:
"Am I currently running in a browser?"
A browser boundary answers a broader rendering question:
"This component requires browser capabilities."
That distinction becomes increasingly useful in applications combining server and client rendering.
A Common Server Rendering Failure
Consider this component:
function ScreenSize() {
const width = window.innerWidth;
return <p>Width: {width}px</p>;
}
During server rendering:
ReferenceError: window is not defined
The server cannot evaluate the component because window does not exist.
A common attempt to fix this is:
function ScreenSize() {
if (typeof window === "undefined") {
return null;
}
return <p>Width: {window.innerWidth}px</p>;
}
This prevents the immediate exception, but it can introduce another problem.
The server may render:
<p></p>
while the browser initially expects:
<p>Width: 1440px</p>
That difference can contribute to hydration inconsistencies.
The better solution is to design the component so its rendering behavior is intentionally compatible with the server/client lifecycle.
Using useEffect() for Browser APIs
For many cases, you do not need a browser-only rendering boundary at all.
You can render a server-safe value and read the browser API after hydration:
import { useEffect, useState } from "react";
function ScreenSize() {
const [width, setWidth] = useState(null);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return (
<p>
{width === null
? "Detecting screen size..."
: `Width: ${width}px`}
</p>
);
}
This pattern has an important advantage: the initial server and browser render can use the same output.
Once hydration completes, the effect reads the browser API and updates the UI.
This is often preferable when the browser-only information is an enhancement rather than a requirement.
When browser() Is More Appropriate
There are cases where a component fundamentally depends on browser capabilities.
Examples include components that require:
Web APIs
Browser storage
DOM measurement
Browser event sources
Media devices
Client-specific APIs
APIs that cannot reasonably be represented during server rendering
For example:
import { browser } from "react";
function CameraControls() {
browser();
async function startCamera() {
const stream = await navigator.mediaDevices.getUserMedia({
video: true
});
console.log(stream);
}
return (
<button onClick={startCamera}>
Start Camera
</button>
);
}
The component has a direct dependency on browser functionality.
Keeping that dependency explicit makes the component's rendering requirements easier to understand.
Browser-Only Code Should Still Be Event-Driven
A browser-only component does not mean browser APIs should be accessed during every render.
For example, avoid unnecessary work like:
function LocationStatus() {
browser();
const position = navigator.geolocation.getCurrentPosition(...);
return <div>Location</div>;
}
Instead, initiate browser operations from an event handler or an effect:
import { browser } from "react";
import { useState } from "react";
function LocationStatus() {
browser();
const [location, setLocation] = useState(null);
function getLocation() {
navigator.geolocation.getCurrentPosition(position => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude
});
});
}
return (
<div>
<button onClick={getLocation}>
Get Location
</button>
{location && (
<p>
{location.latitude}, {location.longitude}
</p>
)}
</div>
);
}
This keeps the browser API interaction tied to an explicit user action.
Avoid Mixing Server and Browser Responsibilities
A useful architecture is to separate data retrieval from browser-specific presentation.
For example:
function ProductPage({ product }) {
return (
<>
<ProductDetails product={product} />
<ProductInteractions productId={product.id} />
</>
);
}
The product data can be rendered on the server:
function ProductDetails({ product }) {
return (
<section>
<h1>{product.name}</h1>
<p>{product.description}</p>
</section>
);
}
Browser-specific functionality can remain isolated:
import { browser } from "react";
function ProductInteractions({ productId }) {
browser();
function addToWishlist() {
// Browser-specific interaction.
}
return (
<button onClick={addToWishlist}>
Add to Wishlist
</button>
);
}
This separation makes the rendering architecture easier to maintain.
Browser-Only Components and Hydration
Hydration connects server-rendered HTML with React running in the browser.
A common mistake is assuming that browser-only rendering automatically eliminates hydration concerns.
It does not.
You still need to ensure that the application has a sensible initial state.
For example, code based on:
const isDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
can produce different values depending on where it runs.
If the server cannot know the browser's preference, the application should establish a consistent initial rendering strategy and then update the UI when the browser value becomes available.
For example:
import { useEffect, useState } from "react";
function ThemeIndicator() {
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
const media = window.matchMedia(
"(prefers-color-scheme: dark)"
);
setDarkMode(media.matches);
}, []);
return (
<span>
{darkMode ? "Dark mode" : "Light mode"}
</span>
);
}
The goal is not simply to avoid an exception. The goal is to maintain predictable server and client rendering.
Common Mistakes
Accessing Browser APIs at Module Scope
This is risky:
const width = window.innerWidth;
export function Dashboard() {
return <p>{width}</p>;
}
The module itself can be evaluated during server rendering.
Move browser-specific work into an appropriate browser-only component, event handler, or effect.
Using typeof window Everywhere
Repeated checks such as:
if (typeof window !== "undefined") {
...
}
throughout a codebase make the rendering model harder to understand.
Prefer clear boundaries between server-compatible and browser-dependent code.
Reading Browser State During Initial Render
Avoid making the first render depend on values the server cannot know.
Examples include:
window.innerWidth
localStorage.getItem("theme")
navigator.language
When these values affect the rendered output, carefully design the hydration behavior.
Assuming browser() Replaces useEffect()
It does not.
These solve different problems.
browser() identifies a browser-only component.
useEffect() controls when side effects execute.
They can be used together when appropriate.
Troubleshooting
window is not defined
Search the component and its imported modules for browser globals:
window
document
navigator
localStorage
sessionStorage
Remember that the problem can originate from a dependency imported by the component, not necessarily from the component's own code.
Hydration Mismatch
Check whether the initial browser render produces different output from the server.
Pay particular attention to:
Random values
Current time
Browser dimensions
Browser storage
Locale information
Media-query state
Move client-specific updates into effects or isolate genuinely browser-dependent components.
Component Works in Development but Fails During Deployment
Development environments can sometimes hide rendering differences because of their client-side behavior.
Test the application using the same server-rendering path used by production.
Make sure dependencies imported by browser-only components are also safe for the intended rendering environment.
Best Practices
Keep browser-specific functionality isolated.
Do not access
windowordocumentat module scope in server-rendered applications.Use effects for browser-side side effects that do not need to affect the initial render.
Use browser-only boundaries for components that fundamentally require browser capabilities.
Keep server-rendered data and client-side interactions separate where practical.
Design a consistent initial state for browser-dependent UI.
Test both server rendering and client hydration.
Check third-party dependencies for server-rendering compatibility.
Avoid unnecessary client-only rendering when a server-safe implementation is possible.
Treat hydration correctness as part of application correctness, not just a warning to suppress.
Advantages and Disadvantages
Advantages
Makes browser-only rendering requirements explicit.
Helps separate server-compatible and browser-dependent components.
Reduces reliance on scattered environment checks.
Works well for components that fundamentally depend on browser capabilities.
Makes rendering architecture easier to reason about.
Disadvantages
Browser-only components cannot provide the same server-rendering benefits as server-compatible components.
Developers still need to understand hydration.
Browser-only boundaries do not automatically solve inconsistent initial state.
Existing third-party packages may still contain server-incompatible code.
Some components can be unnecessarily moved to the browser when a simpler
useEffect()solution would work.
browser() or useEffect()?
A practical decision can be made with one question:
Does the component fundamentally require a browser to exist?
If the answer is no, prefer server-compatible rendering and use useEffect() for browser-only side effects.
If the answer is yes, a browser-only component boundary is more appropriate.
For example, reading the current viewport width is usually an enhancement:
useEffect(() => {
setWidth(window.innerWidth);
}, []);
A component built entirely around a browser API may have a stronger reason to be browser-only:
browser();
The distinction helps avoid turning an entire application into client-only code simply because one small feature needs a browser API.
Conclusion
Server rendering and browser APIs solve different parts of a React application's lifecycle. Problems occur when browser-dependent code is allowed to execute where no browser exists.
React 19.3's browser() provides an explicit way to identify components that require browser capabilities. This makes the rendering boundary clearer and can reduce the need for scattered environment checks.
However, it should not become a replacement for every server-rendering technique. Many browser-dependent features can still be handled cleanly with useEffect(), event handlers, and server-safe initial states.
For production applications, the best approach is to keep server rendering where it provides value, isolate genuinely browser-dependent components, and treat hydration as an important part of the application's architecture. The result is a React application that can use browser APIs without making server rendering unnecessarily fragile.

Join the conversation! Your thoughts help the community grow.