Skip to main content
C
CIPHRX
All articles
Engineering··Updated February 20, 2025·3 min read

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%.

C
CIPHRX
Engineering team

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 window or document
  • 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:

  1. Leaf components first: Convert presentational components that have no client dependencies
  2. Page shells: Move layouts and shells to RSC, keeping interactive islands as client components
  3. Data layers: Migrate API calls to server-side fetch with explicit cache controls
  4. 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.

Next.jsReactPerformanceArchitecture