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
asyncand 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
childrenor 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.