Know which component runs where, add interactivity with "use client", and compose server and client pieces together.
Why: every component in app/ is a Server Component unless you say otherwise. Server Components run on the server only — great for fetching data, using secrets, and shipping less JavaScript to the browser. They cannot use state or onClick.
// app/page.tsx — a Server Component (no directive needed)
import { getProducts } from '@/lib/products'
export default async function Page() {
const products = await getProducts() // runs on the server
return <p>{products.length} products</p>
// process.env.SECRET would be safe to use here — it never reaches the browser
}Why: anything interactive — state, event handlers, useEffect, browser APIs like localStorage — needs a Client Component. Put the string "use client" at the very top of the file to opt in.
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count} likes</button>
}Why: "use client" makes that file AND everything it imports part of the browser bundle. So mark only the interactive leaf (a search box, a button) — not the whole layout — to keep the JavaScript you ship small.
// app/layout.tsx — stays a Server Component
import Logo from './logo' // Server Component
import Search from './search' // Client Component ('use client' inside)
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<Search /> {/* only this island is interactive */}
</nav>
<main>{children}</main>
</>
)
}Why: fetch on the server, then hand the result to a Client Component through props. The only rule: props must be serializable (plain data — strings, numbers, arrays, objects), not functions or class instances.
// app/[id]/page.tsx — Server Component
import LikeButton from './like-button'
import { getPost } from '@/lib/data'
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post = await getPost(id)
return <LikeButton likes={post.likes} /> // likes is a number — serializable
}Why: a Client Component can’t import a Server Component, but it CAN accept one as children. This "slot" pattern lets you keep server-rendered content (like a data-heavy cart) inside client UI (like a modal).
// app/ui/modal.tsx
'use client'
export default function Modal({ children }: { children: React.ReactNode }) {
return <div className="modal">{children}</div>
}
// app/page.tsx — Server Component passes <Cart/> as children
import Modal from './ui/modal'
import Cart from './ui/cart' // a Server Component
export default function Page() {
return (
<Modal>
<Cart />
</Modal>
)
}