Mistake 1: 'use client' at the top of the tree
Marking a layout or page as client turns everything below it into Client Components. You lose the server defaults for the entire subtree. Always promote the smallest leaf.
Mistake 2: Importing server-only modules from a Client file
Modules that touch the database, secrets, or Node-only APIs should be marked with import 'server-only'. If a Client Component imports them, the build fails — loud, fast, fixable.
Mistake 3: Building API routes for everything
App Router doesn't need an /api/… route to fetch data — Server Components do that directly. It doesn't need one for mutations either — Server Actions do that. Route Handlers exist for webhooks, third-party integrations, and genuinely public APIs. Internal CRUD belongs in Actions.
Mistake 4: Passing class instances as props
ORM models, custom Date subclasses, fancy Result types — they don't survive the serialization boundary. The plain fields cross; methods are gone. Convert to plain data before crossing.
Make illegal import directions fail early. Use server-only for secret-bearing modules, keep interaction leaves explicit, and reserve Route Handlers for real HTTP consumers. Convert rich domain objects before transfer instead of trusting accidental serialization.
Boundary discipline does not require a wrapper file for every component. Too many one-line layers make ownership harder to trace. A small set of clear directions—server data, client interaction, shared pure code—is more durable than folder ceremony. Intentionally import a server-only module from a client file and pass an ORM instance through a prop. Keep both failure cases as regression tests. Replace one internal API round trip and verify that authentication and cache policy did not disappear with the boilerplate.