docs/oss/migrating/migrating-to-rsc.md
This guide covers the React-side challenges of migrating an existing React on Rails application to React Server Components (RSC). It focuses on how to restructure your component tree, handle Context and state management, migrate data fetching patterns, deal with third-party library compatibility, and avoid common pitfalls.
[!NOTE] Summary for AI agents: Use this page when the user has an existing React on Rails app and wants to adopt RSC. This covers the React-side migration (component restructuring, state, data fetching). For the initial RSC setup, see the RSC tutorial. RSC requires Pro with the Node renderer.
React on Rails Pro required: RSC support requires React on Rails Pro 4+ with the node renderer. The Pro gem provides the streaming view helpers (
stream_react_component,rsc_payload_react_component), the RSC webpack plugin and loader, and theregisterServerComponentAPI. For setup, see the RSC tutorial. For upgrade steps, see the performance breakthroughs guide.
React Server Components offer significant performance benefits when used correctly:
However, these benefits require intentional architecture changes. Simply adding 'use client' everywhere preserves the status quo -- 'use client' is a boundary marker, not a component annotation. The guides below walk you through the restructuring needed to capture real gains.
This migration guide is organized as a series of focused articles. We recommend reading them in order, but each is self-contained:
How to set up the RSC infrastructure before migrating any components. Covers:
'use client' to all existing component entry points (so nothing changes yet)How to restructure your component tree for RSC. Covers:
'use client' to leaves)How to handle React Context and global state in an RSC world. Covers:
'use client' provider components)React.cache() as a server-side alternative to ContextHow to migrate from client-side data fetching to server component patterns. Covers:
useEffect + fetch with async Server Componentsuse() hook and SuspenseHow to handle libraries that aren't RSC-compatible. Covers:
'use client' wrapper filesserver-only and client-only packagesHow to debug and avoid common problems. Covers:
How to optimize RSC Flight payload size for better performance. Covers:
Before diving into the React patterns, understand how RSC maps to React on Rails' architecture.
Multiple component roots. Unlike single-page apps with one App.jsx root, React on Rails renders independent component trees from ERB views. Each react_component or stream_react_component call is a separate root. You migrate per-component, not per-app.
Three API changes per component. Each component you migrate touches three layers:
| Layer | Before | After |
|---|---|---|
| ERB view helper | react_component("Product", ...) | stream_react_component("Product", ...) |
| JS registration | ReactOnRails.register({ Product }) | registerServerComponent({ Product }) (in all three bundles) |
| Controller | Standard Rails controller | Add include ReactOnRailsPro::Stream |
Three webpack bundles. RSC requires separate client, server, and RSC bundles. The registerServerComponent API behaves differently in each:
Setup instructions: For webpack configuration, bundle structure, route setup, and step-by-step instructions, see the React on Rails Pro RSC tutorial. This guide focuses on the React-side patterns you'll need after setup is complete.
Tailored for React on Rails' multi-root architecture:
'use client' to all component entry points, and switch to streaming rendering. The app works identically -- nothing changes yet.'use client' from the root component to its interactive children, letting parent components become Server Components.stream_react_component for streaming SSR, and server-side data fetching.This approach lets you migrate incrementally, one component at a time, without ever breaking your app.
Before you start, audit your components using this classification:
| Category | Criteria | Action |
|---|---|---|
| Server-ready (green) | No hooks, no browser APIs, no event handlers | Remove 'use client' -- these are Server Components by default |
| Refactorable (yellow) | Mix of data fetching and interactivity | Split into a Server Component (data) + Client Component (interaction) |
| Client-only (red) | Uses useState, useEffect, event handlers, browser APIs | Keep 'use client' -- these remain Client Components |
Before starting any component migration, verify these items. Skipping them is the most common source of wasted debugging time:
react and react-dom at 19.x, with matching versions (yarn why react shows no duplicates)NodeRenderer, not ExecJS. If config.server_renderer is not set to "NodeRenderer", migrate firstreact-on-rails-rsc 19.0.4+ -- earlier versions vendored stale React builds. Check with yarn why react-on-rails-rscreact-client-manifest.json and react-server-client-manifest.json exist in your webpack output directoryrsc_payload_route in config/routes.rbHMR=true RSC_BUNDLE_ONLY=true bin/shakapacker --watch)These mistakes account for the majority of setup failures:
| Mistake | Symptom | Fix |
|---|---|---|
Missing rsc_payload_route in routes | 404 on RSC payload requests | Add rsc_payload_route to config/routes.rb |
| Only 2 webpack bundles (forgot RSC) | Components remain Client Components after removing 'use client' | Create rscWebpackConfig.js and add to build pipeline (Step 4) |
'use client' on bundle entry files instead of component files | Can't migrate components individually | Move 'use client' to each component source file (Step 5) |
Missing 'use client' on .server.jsx files | Auto-bundled components break after enabling RSC | .server.jsx is a bundle convention, not an RSC designation -- add 'use client' to both .client.jsx and .server.jsx |
React version duplicates in node_modules | Cryptic hook errors, "Invalid hook call" | Deduplicate with yarn why react and webpack aliases |
Not switching to stream_react_component | No streaming benefits, components render synchronously | Replace react_component with stream_react_component in views |
Missing include ReactOnRailsPro::Stream in controller | stream_view_containing_react_components undefined | Add the concern to controllers that render React components |
react_component_hash with React 19's built-in <title>, <meta>, and <link> hoisting. Native metadata works with streaming and RSC out of the box.