In Next.js App Router every component is a Server Component until you say otherwise. Learn the one-line 'use client' boundary, why ssr:false blows up inside a server file, and why a stray CSS @import silently dies — the three traps that eat the most hours when you vibe-code your first Next app.
Your stack default for the portfolio apps is Next.js on Cloudflare (CLAUDE.md, Tech stack). The single most common 'it worked in the chat, broke in my terminal' moment for a non-engineer shipping a Next app is the Server/Client boundary — the error text is cryptic, and Claude will happily generate code that trips it. Knowing the boundary turns a 40-minute stall into a 10-second fix.
Next.js (App Router) renders components on the SERVER by default. A Server Component runs once, on the machine, and ships finished HTML to the browser — it's fast and it can touch secrets, databases, and the filesystem. The catch: it has no browser. There is no useState, no useEffect, no onClick, no window. Those only exist after the page reaches a user's browser.
To opt a file into the browser world you put the literal string 'use client' as the very first line of the file. That marks it — AND everything it imports — as a Client Component: now useState/onClick work, but you've lost direct server access. The mental model: 'use client' is a border crossing. Everything above the line in your import tree is server; everything below is browser. You want that border as LOW as possible — make the big page a Server Component and push only the interactive leaf (a button, a toggle) across the line.
Two famous traps live right on that border. (1) next/dynamic with { ssr: false } means 'never render this on the server' — but only a Client Component is allowed to say that. Put it in a Server Component and Next throws the #2065-class error: 'ssr: false is not allowed with next/dynamic in Server Components.' (2) A CSS @import only works if it's the very first rule in the file — one declaration above it and the browser silently ignores the import, and your font or reset just... doesn't load. No error, just wrong.
The error and its fix, minimal. This is the exact shape Claude-generated code trips on:
// app/page.tsx — a Server Component (no 'use client' at top)
import dynamic from 'next/dynamic'
// ❌ THROWS: ssr:false not allowed in a Server Component
const Chart = dynamic(() => import('./Chart'), { ssr: false })
export default function Page() {
return <Chart />
}
// ---- THE FIX: move the dynamic import into a client wrapper ----
// app/ChartLoader.tsx
'use client' // ① border crossing, first line
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./Chart'), { ssr: false }) // ② now legal
export default function ChartLoader() { return <Chart /> }
// app/page.tsx — stays a Server Component, just renders the wrapper
import ChartLoader from './ChartLoader'
export default function Page() { return <ChartLoader /> }
'use client' is a one-way border crossing in your import tree — keep it as low as possible so the big page stays server-fast and only the interactive leaf goes to the browser.