Next.js Components

Decide between Server and Client Components, and place the client boundary to keep pages fast.

TL;DR

  1. Start with Server Components and add 'use client' only when needed.
  2. Push the 'use client' boundary down to the smallest interactive component.
  3. Wrap context providers in a Client Component, then render them in layout.tsx.

Choosing a Component Type

    Server Component

    Use 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 Component

    Use 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 APIs

    Read 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 Spread

    Everything 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 Down

    Move 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 Slot

    Let 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 Provider

    Put 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 Wrapper

    Re-export a hook-based library component from a file marked 'use client'.

    // components/Carousel.tsx
    'use client';
    
    export { Carousel } from 'some-carousel-lib';
    next/dynamic

    Skip 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

    Props

    Fetch 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 Prop

    Pass 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

  1. Split a large interactive component in two, so the static markup stays on the server and only the 'use client' part ships JavaScript.
  2. Render context providers as deep as possible in layout.tsx, so Next.js can still optimize the static parts above them.

Warnings

  1. Every module imported into a 'use client' file joins the client bundle, so one misplaced directive can ship a whole library.
  2. Reading window or localStorage during render throws on the server, so access them inside useEffect instead.

In Practice

FAQ