The default is Server. Promote on hard requirement only.
Walk every component you write through this tree:
Does it need...
├── useState / useEffect / useReducer? → Client
├── onClick / onChange / onSubmit? → Client
├── Browser APIs (window, navigator)? → Client
├── A library that requires hooks? → Client
└── None of the above? → Server ✓
What "mostly server" looks like
In a typical Next.js app, 80–90% of components are Server Components. Pages, lists, cards, headers, footers, articles, tables — all server. Only the leaves that hold state or event handlers wear 'use client'.
Real examples
- Blog post page (renders content) — Server.
- Search bar (controlled input) — Client.
- Markdown renderer (data shape) — Server.
- Theme toggle (writes to localStorage) — Client.
- Like button (optimistic UI) — Client wrapper, server data feed.
Use browser-only requirements as the decision tree: state, effects, events, browser APIs, or hook-based libraries require a client boundary. Pure display, data access, secret access, and heavy shaping remain server work. This produces a server-heavy tree as an outcome, not a quota. An editor or canvas may legitimately be client-heavy. Chasing an 80–90% server ratio can create more network chatter and awkward state than it saves. Optimize for the workload and security boundary, not for a fashionable percentage.
Annotate ten components with the exact requirement that determines their side, then compare those notes with current directives. Move one unjustified boundary and verify bundle size, initial HTML, and interaction rather than declaring success from the file count.