Server and Client Components

Next.js · Essentials · 14 min

Every architectural decision in an App Router app follows from where each component runs.

In the App Router every component is a Server Component unless marked otherwise. Server Components:

  • run on the server (at build time or per request) and send no JavaScript for themselves to the browser,
  • can be async and access databases, files and secrets directly,
  • cannot use state, effects, event handlers or browser APIs.

For interactivity, create a Client Component by putting 'use client' at the top of a file. That file and everything it imports become part of the client bundle. Client Components are still pre-rendered to HTML on the server and then hydrated in the browser.

Composition rules:

  • Server Components can import and render Client Components, passing serialisable props (strings, numbers, plain objects, arrays, Dates, Promises, JSX, Server Actions), but not ordinary functions or class instances.
  • Client Components cannot import Server Components, but can receive them as children or other JSX props. This is how you wrap server-rendered content in a client provider or modal.
  • Push 'use client' to the leaves: make the button interactive, not the whole page, to keep bundles small.
  • Context providers are Client Components; render them in a layout and pass the rest of the tree as children.

Mark modules that must never reach the browser with import 'server-only'; the build fails if a Client Component imports them.

Example

// app/posts/[id]/page.tsx - Server Component (default)
import { LikeButton } from './like-button'
import { getPost } from '@/lib/posts'      // server-only data access

export default async function PostPage({ params }: PageProps<'/posts/[id]'>) {
  const { id } = await params
  const post = await getPost(id)          // runs on the server, no API route needed
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
      <LikeButton postId={id} initialLikes={post.likes} />   {/* serialisable props */}
    </article>
  )
}

// app/posts/[id]/like-button.tsx - Client Component
// 'use client'
// import { useState } from 'react'
// export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
//   const [likes, setLikes] = useState(initialLikes)
//   return <button onClick={() => setLikes(likes + 1)}>Like ({likes})</button>
// }

Remember

  • Components are Server Components by default: async, server-only, no client JS
  • 'use client' marks a boundary; props crossing it must be serialisable
  • Pass Server Components into Client Components as children; keep client islands small

Watch out for

  • Adding 'use client' to a page or layout, turning its whole subtree into client code.

Check yourself

  1. A Server Component wants to give a Client Component an onClick handler defined in the server file. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/getting-started/server-and-client-components