Server and client, working together
Build the café's blog: the list page reads search params on the server, a small Client Component search box updates the URL as you type, and post pages show a friendly 404 for unknown posts.
Splitting server and client work
Notice how little client JavaScript the blog needs — that is the App Router working exactly as it was designed to.
The blog shows the App Router's division of labour at its clearest. The list page is a Server Component: it reads q from searchParams, queries the posts and renders HTML. The search box is a tiny Client Component: it only turns typing into URL changes. Each post page is a dynamic Server Component that calls notFound() for unknown slugs. The browser downloads JavaScript for just the search box.
app/blog/
├── page.jsx Server: await searchParams → getPosts({ q }) → list
├── SearchBox.jsx Client: useSearchParams + router.replace
├── [slug]/page.jsx Server: await params → getPost → notFound()
└── not-found.jsx Friendly 404 for unknown postsBecause the list reads the URL, typing "momo" in the box, refreshing, and sharing the link all show the same results — no state is lost, and there's no separate API to build.