Published on

React Server Components: Practical Lessons One Year In

React Server Components (RSC) are no longer experimental curiosities—they are the default path in modern React frameworks. A year of production use has produced clear winners and sharp edges.

Mental model (still the hard part)

  • Server Components: render on server, zero client JS for their logic, can fetch close to data.
  • Client Components: interactivity, browser APIs, state, effects—marked with 'use client'.
  • The boundary is a bundle contract, not a stylistic choice.
app/
  layout.tsx          ← Server (data + shell)
  page.tsx            ← Server (fetch posts)
  LikeButton.tsx      ← Client (onClick)

What worked well

  • Colocated data fetching without waterfall client hooks
  • Smaller client bundles when islands of interactivity are small
  • Streaming UI for slow backends—perceived performance wins

What hurt teams

PainCauseFix
“Cannot pass X to Client”Non-serializable propsDTOs; fetch on client if needed
Stale UIAggressive cachingRevalidate tags, cache: 'no-store' where appropriate
Debug confusionSplit stacksLogging on server; React DevTools on client only
Over-fetching in RSC treesGiant server treesSplit routes; parallel routes sparingly

Decision checklist

Put on the server when:

  • No user event handlers in that subtree
  • Data is trusted and can stay on server
  • SEO or TTFB matters

Put on the client when:

  • Real-time updates, drag-drop, media APIs
  • Heavy charting or WebGL
  • Offline or optimistic UX

Performance note

RSC does not replace good caching policy. Treat fetch cache, CDN headers, and database indexes as one system. Server rendering a slow query is still slow—it just hides the spinner differently.

Learning investment

If you maintain a Next.js or React Router RSC app, spend one sprint documenting your team’s boundary rules in a short ADR. Future you will thank present you when the fifteenth “use client at the root” PR appears.