We shipped a React 19 app to production in March 2026. The client bundle dropped from 420 KB to 198 KB gzipped. Time to First Byte fell 38%. The only change? Moving 60% of our component tree to React Server Components. No rewrite. No new infrastructure. Just a mental model shift that paid off immediately.
What RSC Actually Means in 2026
React Server Components run exclusively on the server. They never hydrate. They send serialized HTML and a minimal JSON payload describing client component boundaries. The browser receives zero JavaScript for server components — no framework runtime, no component code, no dependencies. Only interactive islands (buttons, forms, charts) ship client bundles.
Bundle Size: The Numbers Don't Lie
| Metric | Before RSC | After RSC | Change |
|---|---|---|---|
| Client JS (gzipped) | 420 KB | 198 KB | -53% |
| Main thread blocking | 890 ms | 310 ms | -65% |
| TTFB (p95) | 1.2 s | 740 ms | -38% |
| LCP (p75) | 2.8 s | 1.9 s | -32% |
The 53% bundle reduction came from removing charting libraries, date pickers, and heavy utility functions from the client bundle. RevenueChart uses a server-side canvas renderer. DatePicker stays client-side but loads lazily. The pattern: keep heavy computation and large dependencies on the server.
Migration Strategy: Incremental, Not Big Bang
We migrated route by route over six weeks. Start with leaf pages — settings, help, static marketing pages. These have zero interactivity and yield immediate bundle wins. Then move to data-heavy read-only views: dashboards, reports, listings. Leave complex forms and real-time features for last.
Operational Patterns That Work
Treat the server as the default rendering surface. Design component APIs around data locality — pass serialized data down, not fetcher functions. Split large routes into cacheable segments using Next.js 15's route segment config. Audit bundle output weekly with next-bundle-analyzer and @next/bundle-analyzer.
"The best RSC implementations in 2026 treat the server as the default rendering surface and the client as a constrained interaction layer.
— React Core Team, 2026 Patterns Guide
Gotchas We Hit
Context doesn't cross the server/client boundary. We replaced global providers with explicit props. Third-party libraries expecting window broke — we wrapped them in dynamic imports with ssr: false. Streaming SSR requires Suspense boundaries at every async component. Missing boundaries cause layout shift.
✦










