We migrated three production apps onto React 19 + the Next.js App Router this quarter. Here is what actually changed and what stayed the same.
Server components, but selectively
We default to server components for layout, content, and data fetching, and reach for client components only when we genuinely need interactivity. The boundary discipline is the win — not the framework.
Patterns that paid off
- Co-locating data fetches with the server component that renders them
- Treating client components as small islands inside a server tree
- Using suspense boundaries to keep first paint fast
Patterns we abandoned
Global state libraries lost most of their job. We still use Zustand, but only for cross-island UI state — never for server-derived data.
If you're reaching for a state library to share data between components in the same tree, the boundary is probably wrong.
Performance
Real-world LCP improved across all three apps. The biggest factor was not the framework — it was the audit we did during the migration. The framework just made the audit cheaper.
Amara designs MCP servers and platform infrastructure. Loves observability, hates flaky tests.
Discussion (1)
Be kind. Be specific.- EFEitan F.Mar 10, 2026
The 'islands inside a server tree' framing is going in our team handbook.
