-
Notifications
You must be signed in to change notification settings - Fork 373
Add React Server Components with React on Rails Pro #722
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
4d09e13
4c0df6e
2f3c42c
ae420af
05cb2bc
0d8d75a
aaeba86
92306ff
3f7e452
1022234
bc41706
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,6 +38,8 @@ def simple; end | |
|
|
||
| def rescript; end | ||
|
|
||
| def server_components; end | ||
|
|
||
| private | ||
|
|
||
| def set_comments | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <%= append_javascript_pack_tag('rsc-client-components') %> | ||
| <%= react_component("ServerComponentsPage", | ||
| prerender: false, | ||
| trace: true, | ||
| id: "ServerComponentsPage-react-component-0") %> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| 'use client'; | ||
|
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading |
||
|
|
||
| // Compare to ./RouterApp.client.jsx | ||
| import { Provider } from 'react-redux'; | ||
| import React from 'react'; | ||
| import { StaticRouter } from 'react-router-dom/server'; | ||
| import ReactOnRails from 'react-on-rails'; | ||
| import ReactOnRails from 'react-on-rails-pro'; | ||
| import routes from '../../../routes/routes.jsx'; | ||
|
|
||
| function ServerRouterApp(_props, railsContext) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // Server Component - this entire component runs on the server. | ||
| // It can use Node.js APIs and server-only dependencies directly. | ||
| // None of these imports are shipped to the client bundle. | ||
|
|
||
| import React, { Suspense } from 'react'; | ||
| import ServerInfo from './components/ServerInfo'; | ||
| import CommentsFeed from './components/CommentsFeed'; | ||
| import TogglePanel from './components/TogglePanel'; | ||
|
|
||
| const ServerComponentsPage = () => { | ||
| return ( | ||
| <div className="max-w-4xl mx-auto py-8 px-4"> | ||
| <header className="mb-10"> | ||
| <h1 className="text-3xl font-bold text-slate-800 mb-2"> | ||
| React Server Components Demo | ||
| </h1> | ||
| <p className="text-slate-600 text-lg"> | ||
| This page is rendered using <strong>React Server Components</strong> with React on Rails Pro. | ||
| Server components run on the server and stream their output to the client, keeping | ||
| heavy dependencies out of the browser bundle entirely. | ||
| </p> | ||
| </header> | ||
|
|
||
| <div className="space-y-8"> | ||
| {/* Server Info - uses Node.js os module (impossible on client) */} | ||
| <section> | ||
| <h2 className="text-xl font-semibold text-slate-700 mb-4 flex items-center gap-2"> | ||
| Server Environment | ||
| <span className="text-xs font-normal bg-emerald-100 text-emerald-700 px-2 py-0.5 rounded-full"> | ||
| Server Only | ||
| </span> | ||
| </h2> | ||
| <ServerInfo /> | ||
| </section> | ||
|
|
||
| {/* Interactive toggle - demonstrates mixing server + client components */} | ||
| <section> | ||
| <h2 className="text-xl font-semibold text-slate-700 mb-4 flex items-center gap-2"> | ||
| Interactive Client Component | ||
| <span className="text-xs font-normal bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full"> | ||
| Client Hydrated | ||
| </span> | ||
| </h2> | ||
| <TogglePanel title="How does this work?"> | ||
| <div className="prose prose-slate max-w-none text-sm"> | ||
| <p> | ||
| This toggle is a <code>'use client'</code> component, meaning it ships JavaScript | ||
| to the browser for interactivity. But the content inside is rendered on the server | ||
| and passed as children — a key RSC pattern called the <strong>donut pattern</strong>. | ||
| </p> | ||
| <ul> | ||
| <li>The <code>TogglePanel</code> wrapper runs on the client (handles click events)</li> | ||
| <li>The children content is rendered on the server (no JS cost)</li> | ||
| <li>Heavy libraries used by server components never reach the browser</li> | ||
| </ul> | ||
| </div> | ||
| </TogglePanel> | ||
| </section> | ||
|
|
||
| {/* Async data fetching with Suspense streaming */} | ||
| <section> | ||
| <h2 className="text-xl font-semibold text-slate-700 mb-4 flex items-center gap-2"> | ||
| Streamed Comments | ||
| <span className="text-xs font-normal bg-amber-100 text-amber-700 px-2 py-0.5 rounded-full"> | ||
| Async + Suspense | ||
| </span> | ||
| </h2> | ||
| <p className="text-slate-500 text-sm mb-4"> | ||
| Comments are fetched directly on the server using the Rails API. | ||
| The page shell renders immediately while this section streams in progressively. | ||
|
justin808 marked this conversation as resolved.
|
||
| </p> | ||
| <Suspense | ||
|
justin808 marked this conversation as resolved.
|
||
| fallback={ | ||
| <div className="animate-pulse space-y-3"> | ||
| {[1, 2, 3].map((i) => ( | ||
| <div key={i} className="bg-slate-100 rounded-lg p-4"> | ||
| <div className="h-4 bg-slate-200 rounded w-1/4 mb-2" /> | ||
| <div className="h-3 bg-slate-200 rounded w-3/4" /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| } | ||
| > | ||
| <CommentsFeed /> | ||
| </Suspense> | ||
| </section> | ||
|
|
||
| {/* Architecture explanation */} | ||
| <section className="bg-slate-50 border border-slate-200 rounded-xl p-6"> | ||
| <h2 className="text-lg font-semibold text-slate-700 mb-3"> | ||
| What makes this different? | ||
| </h2> | ||
| <div className="grid md:grid-cols-2 gap-4 text-sm text-slate-600"> | ||
| <div> | ||
| <h3 className="font-medium text-slate-800 mb-1">Smaller Client Bundle</h3> | ||
| <p> | ||
| Libraries like <code>lodash</code>, <code>marked</code>, and Node.js <code>os</code> module | ||
| are used on this page but never downloaded by the browser. | ||
| </p> | ||
| </div> | ||
| <div> | ||
| <h3 className="font-medium text-slate-800 mb-1">Direct Data Access</h3> | ||
| <p> | ||
| Server components fetch data by calling your Rails API internally — no | ||
| client-side fetch waterfalls or loading spinners for initial data. | ||
| </p> | ||
| </div> | ||
| <div> | ||
| <h3 className="font-medium text-slate-800 mb-1">Progressive Streaming</h3> | ||
| <p> | ||
| The page shell renders instantly. Async components (like the comments feed) | ||
| stream in as their data resolves, with Suspense boundaries showing fallbacks. | ||
| </p> | ||
| </div> | ||
| <div> | ||
| <h3 className="font-medium text-slate-800 mb-1">Selective Hydration</h3> | ||
| <p> | ||
| Only client components (like the toggle above) receive JavaScript. | ||
| Everything else is pure HTML — zero hydration cost. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ServerComponentsPage; | ||
Uh oh!
There was an error while loading. Please reload this page.