Server components by default
The App Router's whole advantage is that components render on the server unless you opt out. Server components fetch data close to where it's used, ship zero JavaScript for the static parts, and keep secrets server-side. Treat `"use client"` as a deliberate exception, not a habit — the moment you mark a high-level component as a client component, its entire subtree ships to the browser.
Keep client components at the leaves
Interactivity belongs at the edges of the tree: a button, a menu, a chart. Render the page on the server, pass data down, and drop small client 'islands' only where the user actually interacts. This keeps the JavaScript bundle small and the page fast.
// page.js — server component (no "use client")
export default async function Page() {
const data = await getDashboard(); // runs on the server
return <Dashboard data={data}><LiveToggle /></Dashboard>;
}
// live-toggle.js — the only client island
"use client";
export function LiveToggle() { /* useState here */ }Cache on purpose, not by accident
Caching is where App Router apps get fast or mysterious. Be explicit per route: static content can be fully cached, dashboards usually shouldn't be, and incremental revalidation suits data that changes occasionally. Don't rely on defaults you haven't read — decide the freshness each route needs and configure it.
When something renders stale or refuses to update, it's almost always a caching decision you didn't make on purpose. Make it explicit and the surprise disappears.
Tenant scope belongs on the server
In a multi-tenant app, resolve the tenant once (middleware/server) and enforce isolation in the data layer — row-level security or strictly scoped queries. Never reconstruct tenant scope in client code; the client can't be trusted to keep tenants apart.
Stream where it improves perceived speed
Use Suspense to stream the slow parts of a page after the fast shell. The user sees content immediately and the expensive widget fills in — better perceived performance without a client-side spinner waterfall.
Key takeaways
- Server-first: client components only at interactive leaves.
- Choose a caching strategy per route — never inherit it by accident.
- Enforce tenant isolation in the data layer, not the client.
- Stream slow sections behind a fast shell for better perceived speed.