Why we moved everything to Next.js 15 Server Components
An honest assessment of React Server Components in production: what works, what breaks, and how we reduced client JS by 84%.
React Server Components changed the default shape of a Next.js app. We've been building on the App Router since it stabilized, and the shift is real — but the cost surface is also real.
Here is what we've learned.
The promise
Server Components let you render UI on the server without shipping component JavaScript to the client. For content-heavy pages, this is transformative. A typical RSC migration of a content-heavy site cuts the parsed-and-executed JavaScript shipped to the browser by an order of magnitude — because most of it never needed to ship in the first place.
LCP drops sharply on content pages, and CLS settles toward zero once you stop hydrating empty shells.
The reality
Server Components are not free. They introduce new constraints:
- No
useState,useEffect, or any client hooks - No browser APIs
- No direct access to
windowordocument - Interleaving server and client components requires mental model shifts
The biggest issue we hit was data fetching. In the Pages Router, getServerSideProps was straightforward. In App Router, you fetch directly in components, but caching semantics are subtle. We accidentally cached user-specific data because we misunderstood cache: 'force-cache'.
Our migration strategy
Migrations rarely benefit from a big-bang rewrite. A strangler-fig pattern works better:
- Leaf components first: Convert presentational components that have no client dependencies
- Page shells: Move layouts and shells to RSC, keeping interactive islands as client components
- Data layers: Migrate API calls to server-side
fetchwith explicit cache controls - Gradual rollout: Feature flags so you can compare pre- and post-migration performance under real traffic
What changes in practice
Concretely, on a content-heavy site you typically see:
- A large drop in shipped client JS — anything that's purely presentational simply isn't sent to the browser anymore
- LCP improves because the HTML is meaningful on first byte
- TTI and INP improve because there's less to hydrate
- Bundle analysis stops being a weekly battle
What still breaks
- Third-party libraries: Many React libraries assume a client environment. Some still need a
"use client"wrapper or a small shim. - Error boundaries: Server Component error handling is still primitive. A thrown error in a deeply nested Server Component can crash the entire page.
- Debugging: Stack traces across the server/client boundary are confusing.
Should you migrate?
If you are building content-heavy sites, dashboards, or internal tools: yes. The performance gains are real and significant.
If you have heavily interactive applications with complex client state: evaluate carefully. The App Router is production-ready, but the ecosystem is still catching up.
We are all-in. The tradeoffs are worth it for our use cases, but go in with open eyes.