App Router foundations

Separate Server and Client Components

Keep components on the server by default and introduce a focused client boundary only when an interface needs browser interactivity.

Pages and layouts in the App Router are Server Components by default. They can read server-side data and secrets without shipping their component code to the browser.

Add "use client" at the top of a file when it needs state, event handlers, effects, context, or browser-only APIs. That directive creates a client boundary for the file and everything it imports. Keep the boundary small. A server page can render a focused interactive child and pass serializable data into it.

On the first request, Next.js uses the server result to produce HTML for a fast initial display and a React Server Component payload for reconciliation. Client Component JavaScript then hydrates the interactive pieces. On later client navigation, the router can fetch the server payload without reloading the whole document.

The directive marks a module boundary, not just one component. Modules imported by that file join the client graph. Putting "use client" high in the tree can ship formatting libraries and otherwise static UI to the browser. Pass the smallest serializable props across the boundary. Functions, database handles, and class instances are not ordinary client props.

// app/counter.tsx
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

Create the Counter and render it from the server home page. Click the button: the count should go 0, 1, 2. Remove "use client" from the counter file and reload. You should get a build or runtime error about hooks in a Server Component. Put the directive back before moving on.

Inspect the browser Network tab for JavaScript chunks, then temporarily move "use client" to page.tsx and compare how much client code loads.

Lesson completed