Next.js Server Components

Render components on the server, fetch data directly, and ship less JavaScript to the browser.

TL;DR

  1. Treat every component in app as a Server Component by default.
  2. Fetch data with await directly inside an async component.
  3. Add 'use client' only to components that need interactivity.

Server Components by Default

    async Component

    Await data inside the component, with no effect or loading state needed.

    export default async function Page() {
      const posts = await getPosts();
      return (
        <ul>
          {posts.map((p) => (
            <li key={p.id}>{p.title}</li>
          ))}
        </ul>
      );
    }
    Direct Data Access

    Query a database directly, since this code never reaches the browser.

    import { sql } from '@/lib/db';
    
    export default async function Users() {
      const users = await sql`SELECT name FROM users`;
      return <p>{users.length} users</p>;
    }
    Private Secrets

    Read server-only environment variables without exposing them to the client.

    export default async function Stats() {
      const res = await fetch('https://api.example.com', {
        headers: { 'x-api-key': process.env.API_KEY! },
      });
      return <p>{await res.text()}</p>;
    }

The Client Boundary

    'use client'

    Mark a file as a Client Component so it can use state and events.

    'use client';
    import { useState } from 'react';
    
    export function Like() {
      const [on, setOn] = useState(false);
      return (
        <button onClick={() => setOn(!on)}>
          {on ? 'Liked' : 'Like'}
        </button>
      );
    }
    Server Renders Client

    Import a Client Component into a Server Component and render it like any other.

    import { Like } from './Like';
    
    export default async function Post() {
      const post = await getPost();
      return (
        <article>
          <h1>{post.title}</h1>
          <Like />
        </article>
      );
    }
    Serializable Props

    Pass plain data across the boundary, such as strings, numbers, arrays, and objects.

    // OK: plain data
    <Chart points={[1, 2, 3]} title="Sales" />
    
    // Error: plain functions cannot cross
    <Chart onSelect={() => save()} />

Composition Patterns

    children

    Pass Server Components through a Client Component by using the children prop.

    // Panel is a Client Component
    <Panel>
      <ServerStats />
    </Panel>
    Leaf Components

    Keep interactive pieces small and low in the tree to ship less JavaScript.

    export default async function Page() {
      const product = await getProduct();
      return (
        <main>
          <h1>{product.name}</h1>
          <AddToCart id={product.id} />
        </main>
      );
    }
    server-only

    Fail the build if a server module is ever imported into client code.

    // lib/data.ts
    import 'server-only';
    
    export async function getSecretData() {
      return fetch(process.env.PRIVATE_URL!);
    }

Data Patterns

    Promise.all()

    Start requests together to avoid a slow one-after-another waterfall.

    const [user, orders] = await Promise.all([
      getUser(id),
      getOrders(id),
    ]);
    cache()

    Share one result across components during a single server render.

    import { cache } from 'react';
    
    export const getUser = cache(async (id: string) => {
      return sql`SELECT * FROM users WHERE id = ${id}`;
    });
    <Suspense>

    Stream a slow component in later while the rest of the page shows.

    import { Suspense } from 'react';
    
    <Suspense fallback={<p>Loading...</p>}>
      <SlowReviews />
    </Suspense>

Tips

  1. Fetch data in the component that uses it, because repeated fetch calls for the same URL are deduplicated within one render.
  2. Import server-only in modules that touch secrets or the database, so a build error stops them from reaching the browser.

Warnings

  1. Server Components cannot use useState, useEffect, or event handlers, so move that logic into a small Client Component.
  2. Props passed to a 'use client' component must be serializable, so plain functions and class instances cause an error.

In Practice

FAQ