The short answer
The App Router makes server rendering the default and client interactivity the exception. Components run on the server unless you opt out, which cuts the JavaScript shipped to the browser — but it means you have to be deliberate about the boundary between server and client, and that boundary is where most migration bugs live.
The traps that catch teams
Passing functions across the boundary
- A server component cannot pass a function — including a component reference — to a client component. React cannot serialise it.
- The fix is a small client wrapper that imports the thing itself.
Assuming everything is dynamic
- Pages are statically built by default. Data that changes needs explicit revalidation, or your users see a snapshot from build time.
Marking too much "use client"
- One "use client" at the top of a tree pulls the whole tree into the browser bundle, quietly undoing the benefit.
What it is genuinely good at
- Content-heavy pages that must render fully without JavaScript — which is also what search engines and AI crawlers read.
- Fetching data where it lives, without an API layer that exists only to serve your own frontend.
- Streaming slow parts of a page without blocking the fast parts.