React on Rails Performance Demo
React on Rails Pro

Deliver Faster Pages
Without Rebuilding Your App

Faster load times. Higher engagement. Lower development costs. React Server Components improve the metrics that drive conversions, SEO, and team velocity.

Traditional SSR
SERVER Fetch all data Render HTML blocked BROWSER Render HTML Download JS Hydrate all parallel response waits for both ~4-6s before anything is interactive
RSC + Streaming
Shell HTML Stream content Minimal JS Selective hydrate instant! tiny bundle all steps overlap ~1.3s interactive
Performance

Faster Pages, Higher Conversions

Your pages load faster because heavy processing happens on the server instead of your users' phones and laptops. Less code to download means faster pages and better Google performance scores.

Traditional — What Ships to Browser
Server 450 KB JS Browser JS bundle contains: App components + hooks + state (170 KB) marked + highlight.js (240 KB) d3 charts + date-fns (40 KB) Everything ships to every user
RSC — What Ships to Browser
Server ~8 KB JS Browser JS bundle contains: Interactive UI (~8 KB) Stays on server (0 KB): marked + highlight.js (240 KB) ✕ d3 + date-fns (40 KB) ✕ Only interactive code ships
Illustration · click for the real per-feature breakdown ↗
Traditional Approach — Your Users Download Everything 450 KB
App Code
Markdown
Charts
Utils
RSC Approach — Only Interactive Elements Reach the Browser ~170 KB
Interactive UI only
Markdown, charts, date formatting → server only, 0 KB to client

Real numbers from this demo: Blog 419 KB → 128 KB · Product 801 KB → 397 KB · all 4 demos compared

Rails View — streams data
post_rsc.html.erb
<%= stream_react_component_with_async_props(
  "BlogPostRSC",
  props: { title: @post.title }
) do |emit|
  # Runs concurrently in background
  # while shell streams to browser

  # Stream content to React component
  emit.call("content", @post.content)

  # Stream related posts
  emit.call("related", @post.related_posts)
end %>
Server Component — 0 KB to client
AsyncBlogContent.tsx
// 0 KB to browser — server only
import { marked } from 'marked';    // 36 KB
import hljs from 'highlight.js'; // 200+ KB

marked.use({
  highlight: (code, lang) =>
    hljs.highlight(code, { language: lang }).value
});

export default async function Content(
  { getReactOnRailsAsyncProp: getAsync }
) {
  const md = await getAsync('content');
  return <article>{marked(md)}</article>;
}

What this means: Your team writes less code, and your users download zero JavaScript for content rendering — the server handles it all. The result is faster pages, better Google performance scores, and lower bandwidth costs.

Streaming

Users See Content Immediately

Traditional rendering makes users stare at a blank page until everything is ready. RSC shows the page layout immediately and fills in content as it becomes available — users see progress, not emptiness.

Traditional SSR
Header Hero Content Product Reviews All at once after long wait
RSC + Streaming
Header (instant) Hero (200ms) Product (500ms) Reviews (1.5s) Streams in one by one
Client-Side Fetching
Browser makes 4 sequential API calls: fetch(/product) fetch(/details) fetch(/reviews) fetch(/related) Problems: Each request has network latency Some requests depend on previous 4 round trips browser ↔ server ~800ms+ of network waterfalls
RSC Server-Side Streaming
Rails streams data from local DB: emit(product) 1ms emit(details) 3ms emit(reviews) 8ms emit(related) 15ms Advantages: Near-zero latency (server ↔ DB) Each emit streams immediately 0 round trips — single HTTP stream ~15ms total, streamed progressively
ProductPage.tsx — Streaming with Suspense
export default function ProductPage({ product }) {
  return (
    <>
      <ProductHeader product={product} />    {/* instant */}
      <Suspense fallback={<Skeleton />}>
        <AsyncReviews />              {/* streams when ready */}
      </Suspense>
      <Suspense fallback={<Skeleton />}>
        <AsyncRecommendations />      {/* streams independently */}
      </Suspense>
    </>
  );
}

Instant Page Layout

Layout, navigation, and structure appear in milliseconds. Users can orient themselves immediately while content-heavy sections load in the background.

Fastest Content First

The most important content renders first. Slower sections stream in independently. Users see real progress, not a frozen screen.

Slow Data Doesn't Block Fast Content

Each page section loads independently. A slow recommendation engine doesn’t block the product details from appearing.

Interactive Elements Work Immediately

Users can tap buttons and fill forms in sections that are already loaded while other content continues streaming in.

RSC vs @loadable/component

Complete Content in Every Page Load

Traditional lazy loading leaves gaps in your initial page — content only appears after JavaScript runs. RSC delivers 100% complete pages on the first load, fully visible to search engines and immediately usable by your customers.

@loadable — Initial HTML Response
Header (SSR'd in HTML) Hero Content (SSR'd in HTML) Reviews (empty until JS loads) requires JS download + hydration Related Items (empty until JS loads) requires JS download + hydration Content missing from initial HTML Invisible to search engines on first crawl
RSC — Initial HTML Response
Header (in HTML, 0 JS) Hero Content (in HTML, 0 JS) Reviews (in HTML, 0 JS) no hydration needed Related Items (in HTML, 0 JS) no hydration needed 100% content in initial HTML Fully visible to search engines on first crawl

The @loadable/component Tradeoff

You can SSR every loadable component — but the architecture forces a painful choice:

1

SSR’ing everything delays TTFB and FCP — @loadable’s ChunkExtractor requires renderToString, which cannot stream. The server must finish rendering all components before sending any HTML.

2

Every SSR’d component still needs JS for hydration — the HTML is visible but frozen until its JavaScript downloads, parses, and executes. More components = more JS = higher TTI.

3

So teams are forced to choose: SSR critical components for fast FCP (non-critical content missing from HTML), or SSR everything for complete HTML (but slow FCP and heavy hydration).

RSC eliminates this tradeoff. Server components are in the initial HTML AND cost zero JavaScript AND require zero hydration. No penalty for rendering everything on the server.

Aspect @loadable/component React Server Components
JavaScript sent to users Full component code in lazy chunks — deferred, not eliminated Zero for server components — truly eliminated
Processing needed in browser Required for every component, including lazy ones after chunks load Only for interactive 'use client' components
Implementation complexity 5 packages + webpack plugin + SWC/Babel plugin + ChunkExtractor + loadableReady Add 'use client' to interactive components. Done.
Optimization approach Manual — wrap each import with loadable() Automatic at every client component boundary
Search engine visibility Lazy content absent from initial HTML — depends on Google’s JS rendering queue All content in initial HTML — indexed on first crawl

Why This Matters for SEO

Google indexes pages in two waves. The first wave reads raw HTML immediately. The second wave renders JavaScript — but with a median delay of ~10 seconds, up to 18 hours at the 99th percentile.

Content behind lazy-loaded components — reviews, recommendations, comments — is exactly the long-tail keyword-rich content that drives organic search traffic. With @loadable, this content is invisible to the first wave. With RSC, it’s all in the initial HTML.

Developer Experience

Faster Development, Lower Engineering Costs

Your developers write dramatically less code — fewer lines means fewer bugs, faster feature delivery, and lower maintenance costs.

useEffect — Moving Parts
useEffect useState x3 loading state error state cleanup fn dep array stale closure cancelled flag API endpoint 8 concepts to manage
async Component — Moving Parts
async function await + return JSX await data return JSX 2 concepts. That's it. React handles loading, errors, streaming
Before — Client Component
Reviews.tsx
'use client';
function Reviews({ productId }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState(null);

  useEffect(() => {
    let skip = false;
    fetch(`/api/reviews/${productId}`)
      .then(r => r.json())
      .then(d => !skip && setData(d))
      .catch(e => !skip && setErr(e))
      .finally(() => setLoading(false));
    return () => { skip = true; };
  }, [productId]);

  if (loading) return <Spinner />;
  if (err) return <Error />;
  return <ReviewList reviews={data} />;
}
After — Server Component
Reviews.tsx
async function Reviews(
  { getReactOnRailsAsyncProp }
) {
  const get = getReactOnRailsAsyncProp;
  const { reviews } = await get('reviews');
  return <ReviewList reviews={reviews} />;
}

// No useState. No useEffect.
// No cleanup. No loading state.
// No error boilerplate.
// No stale closures.
// No dependency arrays.
// No API endpoints.
// Just an async function.

What this means: 20 lines of complex code becomes 5 lines of straightforward code. That translates directly to faster feature delivery, fewer production bugs, and lower ongoing maintenance costs for your engineering team.

Simpler Architecture

Server components access data directly. No REST routes, no GraphQL schemas, no fetch libraries.

No Data Loading Bottlenecks

Data loads in parallel on the server with near-zero latency — no sequential request chains slowing down your pages.

Less Code to Maintain

Server components render once and never re-render. Entire categories of performance optimization code become unnecessary.

React on Rails Pro

Works With Your Rails App

RSC is not Next.js-only. React on Rails Pro is the first framework to bring full React Server Components support to Ruby on Rails.

YOUR RAILS STACK Controllers ActiveRecord Models ERB Views Devise Sidekiq Keeps working as-is REACT ON RAILS PRO Streaming SSR RSC Flight Protocol Async Props Bridge Code Splitting BROWSER Server Components Pre-rendered HTML, 0 JS Client Components Interactive islands, tiny JS Selective Hydration

Keep Your Stack

Controllers, routes, views, authentication — everything stays.

Protect Your Existing Investment

No rewrite to Next.js or any Node framework. Adopt RSC incrementally without disrupting your team.

Works With Your Entire Stack

Devise, Pundit, ActiveRecord, Sidekiq — RSC works alongside every gem and tool your team already relies on.

Full RSC Integration

Streaming SSR, RSC payload embedding, client/server code splitting — all handled for you.

Ready to Deliver Faster Pages?

Faster pages, better SEO, lower engineering costs — without re-platforming your Rails app.