Next.js Components
Decide between Server and Client Components, and place the client boundary to keep pages fast.
TL;DR
- Start with Server Components and add
'use client'only when needed. - Push the
'use client'boundary down to the smallest interactive component. - Wrap context providers in a Client Component, then render them in
layout.tsx.
Choosing a Component Type
Server ComponentUse for data fetching, secrets, and content that does not respond to clicks.
export default async function Price({ id }: Props) {
const { price } = await getProduct(id);
return <p>${price}</p>;
}Client ComponentUse for state, event handlers, effects, and anything that reacts to the user.
'use client';
import { useState } from 'react';
export function Quantity() {
const [qty, setQty] = useState(1);
return (
<input
type="number"
value={qty}
onChange={(e) => setQty(+e.target.value)}
/>
);
}Browser APIsRead window or localStorage inside an effect, never during render.
'use client';
import { useEffect, useState } from 'react';
export function Theme() {
const [theme, setTheme] = useState('light');
useEffect(() => {
setTheme(localStorage.getItem('theme') ?? 'light');
}, []);
return <p>{theme}</p>;
}Placing the Boundary
Boundary SpreadEverything a 'use client' file imports becomes client code as well.
'use client';
// Both of these now ship to the browser
import { Chart } from './Chart';
import { formatDate } from './dates';Push It DownMove the directive to the smallest component that needs interactivity.
// layout.tsx stays a Server Component
import { Search } from './Search'; // 'use client'
export default function Header() {
return (
<header>
<Logo />
<Search />
</header>
);
}children SlotLet a Client Component wrap server-rendered content passed in as children.
'use client';
export function Modal({
children,
}: {
children: React.ReactNode;
}) {
const [open, setOpen] = useState(false);
return open ? <div>{children}</div> : null;
}Providers and Libraries
Context ProviderPut the provider in its own Client Component, since context needs the client.
'use client';
import { createContext } from 'react';
export const ThemeContext = createContext('light');
export function Providers({
children,
}: {
children: React.ReactNode;
}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}Library WrapperRe-export a hook-based library component from a file marked 'use client'.
// components/Carousel.tsx
'use client';
export { Carousel } from 'some-carousel-lib';next/dynamicSkip server rendering for a component that only works in the browser.
'use client';
import dynamic from 'next/dynamic';
const Map = dynamic(() => import('./Map'), {
ssr: false,
});Passing Data Down
PropsFetch on the server and hand the result to a Client Component as props.
export default async function Page() {
const points = await getSales();
return <SalesChart points={points} />;
}Server Function PropPass a Server Function to a Client Component so it can call the server.
import { addToCart } from './actions';
export default function Page() {
return <BuyButton action={addToCart} />;
}use()Pass a promise down and unwrap it in the client with use.
'use client';
import { use } from 'react';
export function Comments({
data,
}: {
data: Promise<Comment[]>;
}) {
const comments = use(data);
return <p>{comments.length} comments</p>;
}Tips
- Split a large interactive component in two, so the static markup stays on the server and only the
'use client'part ships JavaScript. - Render context providers as deep as possible in
layout.tsx, so Next.js can still optimize the static parts above them.
Warnings
- Every module imported into a
'use client'file joins the client bundle, so one misplaced directive can ship a whole library. - Reading
windoworlocalStorageduring render throws on the server, so access them insideuseEffectinstead.
In Practice
Add a context provider to the whole app while the layout itself stays on the server.
- The Providers file is a Client Component because context only works on the client.
- The root layout stays a Server Component and simply renders Providers.
- Pages pass through as children, so they are still rendered on the server.
- Wrapping only children keeps the html and body tags outside the provider.
// app/providers.tsx
'use client';
import { ThemeProvider } from 'next-themes';
type Props = { children: React.ReactNode };
export function Providers({ children }: Props) {
return <ThemeProvider>{children}</ThemeProvider>;
}
// app/layout.tsx
import { Providers } from './providers';
type Props = { children: React.ReactNode };
export default function RootLayout({ children }: Props) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}FAQ
Ask what the component needs. If it only shows data, keep it on the server. If it needs state, effects, event handlers, or browser APIs, add 'use client'.
Many libraries use hooks but do not include 'use client'. Create a small file that starts with 'use client' and re-exports the component. Import from that file instead.
Not directly. A Server Component imported into a 'use client' file becomes client code. Pass it in as children from a Server Component parent instead.