Client-Side Module Marker
The `client-only` package is a specialized marker module within the React Server Components (RSC) ecosystem, primarily used in frameworks like Next.js App Router. Its sole purpose is to indicate to the build system that a module and all its transitive dependencies are intended exclusively for client-side execution. It doesn't export any functions or values, acting purely as a side-effect import. When a module imports `client-only`, compatible bundlers and frameworks will enforce that this module is never included in a server bundle, providing build-time errors for misuse. This package, currently at version `0.0.1` and infrequently updated due to its static nature, complements `server-only` to clearly define server-client boundaries in a hybrid rendering environment. Its key differentiator is its explicit, declarative way of preventing accidental server-side imports of client-specific code, which helps avoid issues like 'window is not defined' errors during server rendering.
Common errors
-
Error: You're importing a component that needs 'client-only'. It only works in a Client Component but you're trying to render it in a Server Component.
cause A module that directly or transitively imports 'client-only' was imported into a React Server Component.fixEnsure the module importing 'client-only' is itself a Client Component (marked with 'use client';) or is only imported by other Client Components. If it's a utility, ensure it's only called from client-side code. -
ReferenceError: window is not defined
cause A Client Component (even one importing 'client-only') attempts to access a browser-specific global object like `window` or `document` during the initial server-side render phase, where these objects do not exist.fixPlace all code that interacts with browser-specific APIs (e.g., `window`, `localStorage`) inside a `useEffect` hook, or guard it with `if (typeof window !== 'undefined')` to ensure it only runs in a browser environment.
Warnings
- gotcha The `client-only` package is a compile-time marker, not a runtime guard. It enforces that the module is bundled only for the client. It does not automatically make server-incompatible code (e.g., direct `window` access) safe if the Client Component still undergoes a server pre-render (SSR) phase. You still need to safeguard browser-specific API calls within `useEffect` or check `typeof window !== 'undefined'` in Client Components to avoid errors during initial server rendering.
- gotcha Importing `client-only` into a module *does not* automatically turn it into a React Client Component. For React, the `'use client'` directive at the top of the file is the primary mechanism to mark a module as a Client Component. `client-only` provides an additional layer of explicit error checking for non-component client-only logic or to explicitly mark a module that *should* be part of the client bundle graph.
- gotcha While `client-only` helps prevent server-side bundling, it does not solve hydration mismatches if your client component renders different content on the server (during SSR) versus the client. Such mismatches can still lead to 'Hydration failed because the server rendered HTML didn't match the client' errors.
- gotcha The `client-only` package itself has no runtime effect and is merely a marker. Some frameworks, like Next.js App Router, have built-in mechanisms to detect client-only code and provide similar error messages. Installing `client-only` might be optional in such cases but can improve linting feedback and explicit intent.
Install
-
npm install client-only -
yarn add client-only -
pnpm add client-only
Imports
- Side-effect import for client-only marking
import { someExport } from 'client-only';import 'client-only';
Quickstart
import 'client-only';
import { useState, useEffect } from 'react';
export default function ClientSideCounter() {
// This component will only run on the client, enforced by 'client-only'
const [count, setCount] = useState(0);
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(typeof window !== 'undefined');
// Example of browser-specific API usage
if (isBrowser) {
console.log('Running on browser. User agent:', window.navigator.userAgent);
}
}, [isBrowser]);
return (
<div>
{isBrowser ? (
<p>You clicked {count} times</p>
) : (
<p>Loading client component...</p>
)}
<button onClick={() => setCount(count + 1)}>Click me</button>
{!isBrowser && <p>This component is client-only and will hydrate soon.</p>}
</div>
);
}