Many developers add 'use client' reactively, when an error appears about hooks in a Server Component, when something "feels interactive," or by habit carried over from the Pages Router era. The result is a codebase where half the component tree has crossed the client boundary without a deliberate choice.
A more reliable approach is to ask the question for each component you write: Should this be a Client Component? This guide gives you a three-question framework for making that call, shows where teams usually get tripped up, and walks through composition patterns that keep most of your tree on the server.
In brief
- The default in the App Router is Server Components.
'use client'is an opt-in marker, not a default state. - Three signals indicate a Client Component: React hooks, event handlers, and browser APIs.
- The directive cascades. Mark a file, and every component imported into it also becomes a Client Component.
- Composition patterns keep boundaries narrow: pass Server Components as
childrenor props into Client Components instead of nesting them through imports.
'use client' in Next.js: The One-Line Summary
Before digging into the framework, it helps to start with a simple mental model so the rest stays clear.
The Directive Marks a Hydration Boundary, Not an SSR Switch
This is the part that tends to cause the most confusion. 'use client' does not turn off server-side rendering. Client Components still render to HTML on the server during the initial request. The Next.js documentation lays out the initial page load sequence clearly:
- The component is prerendered to HTML on the server.
- That HTML is sent to the browser as a non-interactive preview.
- The component's JavaScript bundle is also sent to the browser.
- React hydrates the component, attaching event handlers to the existing DOM.
What the directive actually marks is the boundary where React ships JavaScript to the browser and hydrates it for interactivity. Server Components never undergo hydration: their output is static HTML, and zero JavaScript is shipped to the browser for them. Components within a 'use client' subtree go through the full hydration phase.
On the initial page load in Next.js, Client Components can be pre-rendered on the server and then hydrated on the client; on subsequent client-side navigations, Next.js performs client-side transitions that update content dynamically rather than doing a full page reload.
Sign up for the Logbook, Strapi's Monthly newsletter
The Boundary Cascades Through Imports
Place the directive at the top of a file, and every component imported from that file becomes a Client Component. The React documentation states that 'use client' marks a file and all of its transitive dependencies as client code. This cascade is the most important thing to understand about the directive because it explains why a 'use client' at the wrong level can pull half your component tree across the boundary without you noticing.
'use client';
import { useState } from 'react';
// Every component defined or imported in this file
// is now a Client Component.You do not need to add the directive to every downstream file. The Next.js API reference confirms you only need it at the topmost component in a subtree that requires client features. Everything below that entry point inherits the boundary automatically.
A Decision Framework for 'use client'
Rather than memorizing rules, ask these three questions of every component you build. If the answer to any of them is yes, the component needs 'use client'. If all three are no, it remains a Server Component.
Does it Call a React Hook?
useState, useEffect, useRef, useContext, useReducer, useMemo, useCallback, useLayoutEffect, and any custom hook built on these. The App Router's client-side routing hooks, useRouter, usePathname, useSearchParams, and useParams, also count. State and effects only exist in Client Components.
Does it Attach an Event Handler?
onClick, onChange, onSubmit, onFocus, onBlur, onKeyDown, and the rest of the DOM event family. Even a single button with an onClick requires the directive. Event handlers need hydrated JavaScript to function. They cannot exist in static HTML alone.
Does it Touch a Browser API?
window, document, localStorage, sessionStorage, navigator, IntersectionObserver, and the Geolocation API. Anything that does not exist in the Node.js environment where Server Components execute. These APIs need to be instantiated inside useEffect since they are undefined during server rendering.
Three "No" Answers Means Leave It on the Server
If your component just renders props or fetched data into JSX without state, handlers, or browser APIs, it is a Server Component. Adding it anyway costs bundle size and hydration time for no benefit. Worth noting: Next.js built-in components like <Link>, <Image>, <Script>, and <Form> are all compatible with Server Components and do not require the directive in the consuming file.
Scenarios Where 'use client' is the Right Call
The three-question framework makes these straightforward, but it helps to map them to the kind of work you do every day.
Forms with Validation and Local State
Email inputs that turn red on invalid format, multi-step forms that track progress, password fields with strength meters. Any time you hold form state in useState or respond to onChange for inline validation, the component crosses the boundary.
That said, the <form> element itself can remain in a Server Component using a Server Action as its action prop. Components that use useFormStatus, such as an interactive submit button, are typically the ones marked with 'use client'. Isolate the client boundary to the interactive atom.
Filter, Search, and Sort Interactions
A product grid where users filter by category or sort by price. The grid itself can stay on the server, but the controls that drive it, search bars, dropdowns, and toggle groups, need to be Client Components so they can track input state with useState and respond to onChange. The composition patterns documentation uses a <Search /> component as the canonical example of this split.
Components Using Browser-Only APIs
A component that reads from localStorage to remember a theme preference, or one that uses IntersectionObserver to lazy-load content below the fold. These APIs do not exist during server rendering and must be instantiated inside useEffect after the component mounts.
If your component's core purpose depends on a Web API unavailable in Node.js, it needs the directive. Keeping these as lean leaves helps performance guidance by shipping less client JavaScript.
Wrappers around Client-Only Third-Party Libraries
Most React component libraries, animation libraries, rich text editors, charting libraries, and drag-and-drop libraries depend on hooks or browser APIs internally. If the library ships without a 'use client' directive, importing it directly into a Server Component produces a build error. The fix is a thin wrapper file with the directive:
// components/carousel.tsx
'use client'
import { Carousel } from 'acme-carousel'
export default CarouselServer Components import the wrapper. The directive stays contained, and the consuming page stays on the server. Most teams run into this sooner or later when choosing component libraries for a Next.js project.
Components that Need React Context
Theme providers, auth session context, feature flag context, and cart context. React Server Components do not support creating or consuming context directly. The provider and any component reading from it both need to be Client Components.
Scenarios Where Server Components Are the Right Call
The flip side of the framework. These are the cases where the three-question test returns "no" across the board, and adding the directive just adds cost.
Rendering Content from a CMS or Database
Blog post bodies, product cards, article listings, and marketing sections. These render data and stop. Keeping them as Server Components ships zero JavaScript for the rendering itself and lets the data appear in the initial HTML. No hydration cost, no loading spinners, and no client-side fetch waterfall.
Data Fetching That Can Run on the Server
Server Components can be async functions and await data directly during render. Client Components cannot be async like Server Components and typically fetch data client-side using React patterns such as use, SWR, React Query, or useEffect, which can create a waterfall:
- HTML renders empty.
- JavaScript downloads and hydrates.
- The fetch begins.
Empty initial HTML also means search engines and social previews see nothing useful until JavaScript runs. Server Components sidestep that, and the data arrives in the initial HTML. When paired with React Suspense, you can stream data progressively without blocking the entire page.
Layouts, Navigation, and Shells
A site header, footer, or sidebar that renders links and a logo does not need state. Marking your root layout as a Client Component is one of the most expensive mistakes you can make because every page below it inherits the boundary. The layout shell stays static HTML, and only the interactive leaves, a search bar or a mobile menu toggle, get the directive.
Pages Where the Only "Interactive" Element Is a Link
<Link> from Next.js works fine in Server Components. A page made of headings, paragraphs, images, and links is a Server Component, full stop. No directive needed.
Static or Read-Only Widgets
Pricing tables, comparison grids, FAQ accordions rendered open by default, and testimonial carousels driven by CSS animations. If interactivity is decorative or absent, the component stays on the server. The distinction between static output and interactive behavior matters here. Read-only display is firmly in Server Component territory.
Composition Patterns that Keep the Boundary Narrow
These patterns let you add interactivity without dragging the whole tree into the client bundle.
Push the Directive to Leaf Components
Do not mark a page or layout as 'use client' because one button on it needs an onClick. Extract that button into its own file, mark only that file, and leave the surrounding components on the server. The Next.js documentation recommends keeping Server Components at the top of the tree and rendering Client Components as deep as possible for optimization. The smaller the leaf, the smaller the client bundle.
Pass Server Components as children to Client Components
A Client Component can render Server Components if they arrive as children or props from a Server Component parent. The Server Component is rendered on the server, and its rendered result is passed to the client as part of the React Server Component Payload, where React reconciles it into the Client Component tree.
This works because of module graph mechanics: the Server Component is not statically imported by the 'use client' file. It is imported by the parent Server Component and passed through as an opaque React node. The cascade through props does not propagate.
// Server Component
export default async function Page() {
const articles = await fetchArticles();
return (
<FilterShell>
<ArticleList articles={articles} />
</FilterShell>
);
}Here, FilterShell is a Client Component with 'use client', but ArticleList stays on the server because it is composed in the Server Component parent, not imported inside FilterShell.
Pass Server-Fetched Data as Props
Rather than fetching inside a Client Component with useEffect, fetch in the parent Server Component and pass the data down. The fetch happens once on the server, the data appears in the initial HTML, and the Client Component only handles interactivity. One constraint: props passed across the boundary must be serializable, strings, numbers, plain objects, and arrays. Functions and class instances cannot cross.
Common 'use client' Mistakes to Avoid
Marking the Entire Page as a Client Component
Symptom: a route bundle that is far larger than it should be on a content-heavy page, and export const metadata silently stops working because metadata can only be exported from Server Components. Cause: a 'use client' at the top of page.tsx, which makes that page a Client Component boundary due to one interactive element and can increase the client-side bundle size. Fix: move the interactive element to its own file and mark only that file.
Fetching Data Inside useEffect Instead of on the Server
Symptom: empty initial HTML, loading spinners on every page load, and weak Largest Contentful Paint scores. This is an SPA migration habit, defaulting to useEffect(() => { fetch(...) }, []). Fix: lift the fetch to the nearest Server Component ancestor and pass the data down as a prop. For parallel fetching, use Promise.all to fetch in parallel.
Importing a Server Component into a Client Component
Symptom: a build error or unexpected client-side execution of code you thought ran on the server. Cause: Client Components cannot directly import Server Components because the static import pulls the module into the client graph. Fix: pass the Server Component as children or as a prop from a Server Component parent.
Forgetting that the Boundary Cascades
A 'use client' on a layout file makes that layout itself a Client Component, but nested route segments are not automatically all turned into Client Components. Context providers are a common culprit. Extract them into their own 'use client' file and keep the layout itself as a Server Component. Audit your layouts and providers regularly. This boundary behavior is one of the key architectural aspects described in the React 'use client' reference.
Applying the Framework to a Strapi-Powered Next.js App
When your Next.js frontend is backed by Strapi, the framework strongly favors Server Components for most of the tree. Content pages, article listings, and product grids are exactly the cases where Server Components win on bundle size, SEO, and caching.
Fetch Strapi Content in Server Components
The recommended pattern is an async Server Component that fetches from the Strapi API during render. The response is included in the initial HTML, the API token stays on the server, using process.env.STRAPI_API_TOKEN, not a NEXT_PUBLIC_ variable, and you can use Next.js caching with the next.revalidate option.
// app/articles/page.tsx
async function getArticles() {
const res = await fetch(
`${process.env.STRAPI_URL}/api/articles?populate=*`,
{ next: { revalidate: 60 } }
);
return res.json();
}
export default async function ArticlesPage() {
const { data } = await getArticles();
return <ArticleList articles={data} />;
}For on-demand revalidation updates, configure a Strapi lifecycle hook to call a Next.js Route Handler that invokes revalidateTag.
Use 'use client' Only for Interactive Features Around the Content
Filter bars, search inputs, "add to cart" buttons, comment forms, and review submissions are the leaves that need the directive. The page wrapper, the article body, the product card itself, and the layout shell all stay on the server.
The Strapi data flows from a Server Component fetch down into small interactive shells, not the other way around. Client Components that need to mutate Strapi data should call a Next.js Route Handler that proxies the request server-side, keeping the API token off the client. This pattern applies whether you are building a portfolio or implementing content previews.
Building Reliable Next.js Apps with 'use client'
The directive is a tool, not a default. Run the three-question test on every component, hooks, event handlers, and browser APIs, and push the boundary to the smallest leaf that genuinely needs interactivity. Use composition to keep server-rendered content flowing through interactive shells.
A closing checklist:
- Default to Server Components for content, layouts, and data fetching.
- Mark only the leaves that hold state, attach handlers, or touch browser APIs.
- Pass Server Components as
childrento keep most of the tree off the client. - Audit layouts and providers since the boundary cascades through them.
If your frontend is backed by a headless CMS, this approach pays off even more because content stays in fast, cacheable Server Components while interactivity lives in precisely scoped leaves. The Strapi guide is a natural fit for exactly this architecture.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.