There's one question that unlocks the Next.js App Router: “where does this code actually run?” Once you can tell apart what should stay on the server — like a database secret — from what can only work in the browser — like click state — architecture decisions get a lot easier.
The mental model
Pages and Layouts are Server Components by default. A Server Component can use async/await right next to the data source, and secrets never get sent to the browser. If you need useState, useEffect, event handlers, window, or localStorage, add use client at the top of the file. Once you add a client boundary, that file's entire import tree ends up in the client bundle too — so keep it scoped to the smallest interactive leaf you can.
Let's build it together
// app/page.tsx — Server Component
import { Counter } from "./counter";
import { getNoteCount } from "@/lib/notes";
export default async function Page() {
const count = await getNoteCount();
return <Counter initialCount={count} />;
}
// app/counter.tsx — Client Component
"use client";
import { useState } from "react";
export function Counter({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}How the code works
The page fetches the note count on the server and passes it to Counter as a number prop. Only Counter has state and a click handler, so only it needs browser JavaScript. There's no need to mark the whole page use client.
The initial count from the server appears, and it increases in the browser on every button click.5-Minute Try-It
Split a Server Component profile page from a Client Component follow button.
Next.js — Server and Client Components — Next.js