Skip to content

Commit f1fa1d7

Browse files
ihabadhamclaude
andauthored
Pro RSC migration 3/3: React Server Components demo on webpack
Adds a React Server Components demo page at /server-components, riding on top of Sub-PR 2's NodeRenderer + webpack setup. The page demonstrates four RSC capabilities: - Server Environment: ServerInfo reads Node's `os` module and lodash on the server; neither reaches the browser. - Interactive Client Component: a 'use client' TogglePanel nested inside a Server Component tree, demonstrating the donut pattern. - Live Server Activity: client-driven server-component re-fetching via useRSC().refetchComponent + RSCRoute, with react-error-boundary catching simulated errors and a Retry button that re-primes the cache before resetting the boundary. Local Suspense fallback prevents the in-flight RSC fetch from collapsing the page. - Streamed Comments: async Server Component receiving comments as props from the controller per the canonical Pro data-fetching pattern, streaming in via Suspense after the page shell. Renderer config (renderer/node-renderer.js) needs stubTimers: false (the default `true` no-ops setTimeout, which React's RSC server renderer uses for Flight-protocol yielding — without the override, RSC streams emit zero chunks and hang until the idle timeout fires) and replayServerAsyncOperationLogs: true (surfaces async-Server-Component console output through consoleReplayScript). Build setup uses upstream RSCWebpackPlugin and WebpackLoader. The RSC bundle reuses the server-bundle entry; entry name, RSC loader injection, and a few resolve overrides (react-server condition, react-dom/server: false) are the only differences from the SSR build. The webpack-config dispatch gains an RSC_BUNDLE_ONLY env-var gate matching the existing SERVER_BUNDLE_ONLY / CLIENT_BUNDLE_ONLY pattern; default builds now produce three bundles (client + server + RSC). Covered by request + system specs at spec/requests/server_components_spec.rb and spec/system/server_components_demo_spec.rb. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 767c513 commit f1fa1d7

31 files changed

Lines changed: 714 additions & 4 deletions

File tree

.controlplane/templates/app.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ spec:
3434
value: '2'
3535
- name: RENDERER_URL
3636
value: http://localhost:3800
37+
# Enable the artificial Suspense demo delay so the streaming fallback is
38+
# visible on the review-app. Off by default in production deployments.
39+
- name: RSC_SUSPENSE_DEMO_DELAY
40+
value: 'true'
3741
# RENDERER_PASSWORD and REACT_ON_RAILS_PRO_LICENSE must be created in the
3842
# Control Plane Secret named by {{APP_SECRETS}} before deploy. cpflow
3943
# resolves {{APP_SECRETS}} to `{APP_PREFIX}-secrets` — which means review

Procfile.dev

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ rails: bundle exec thrust bin/rails server -p 3000
1212
wp-client: RAILS_ENV=development NODE_ENV=development bin/shakapacker-dev-server
1313
# Server webpack watcher for SSR bundle
1414
wp-server: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch
15+
# RSC webpack watcher for React Server Components bundle
16+
wp-rsc: RSC_BUNDLE_ONLY=yes bin/shakapacker --watch
1517
node-renderer: NODE_ENV=development node renderer/node-renderer.js

app/controllers/pages_controller.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
class PagesController < ApplicationController
44
include ReactOnRails::Controller
5-
before_action :set_comments
5+
include ReactOnRailsPro::Stream
6+
before_action :set_comments, only: %i[index no_router]
67

78
def index
89
# NOTE: The below notes apply if you want to set the value of the props in the controller, as
@@ -38,6 +39,12 @@ def simple; end
3839

3940
def rescript; end
4041

42+
def server_components
43+
@server_components_comments = Comment.order(id: :desc).limit(10)
44+
.as_json(only: %i[id author text created_at updated_at])
45+
stream_view_containing_react_components(template: "/pages/server_components")
46+
end
47+
4148
private
4249

4350
def set_comments
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<%= stream_react_component("ServerComponentsPage",
2+
props: { comments: @server_components_comments },
3+
prerender: true,
4+
auto_load_bundle: true,
5+
trace: Rails.env.development?) %>

client/app/bundles/comments/components/Footer/ror_components/Footer.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
import React from 'react';
24
import PropTypes from 'prop-types';
35
import BaseComponent from 'libs/components/BaseComponent';

client/app/bundles/comments/components/NavigationBar/NavigationBar.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ function NavigationBar(props) {
102102
Rescript
103103
</a>
104104
</li>
105+
<li>
106+
<a
107+
className={navItemClassName(pathname === paths.SERVER_COMPONENTS_PATH)}
108+
href={paths.SERVER_COMPONENTS_PATH}
109+
>
110+
RSC Demo
111+
</a>
112+
</li>
105113
<li>
106114
<a
107115
className={navItemClassName(false)}

client/app/bundles/comments/components/SimpleCommentScreen/ror_components/SimpleCommentScreen.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
// eslint-disable-next-line max-classes-per-file
24
import React from 'react';
35
import request from 'axios';

client/app/bundles/comments/constants/paths.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export const RESCRIPT_PATH = '/rescript';
55
export const SIMPLE_REACT_PATH = '/simple';
66
export const STIMULUS_PATH = '/stimulus';
77
export const RAILS_PATH = '/comments';
8+
export const SERVER_COMPONENTS_PATH = '/server-components';

client/app/bundles/comments/rescript/ReScriptShow/ror_components/RescriptShow.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
// Wrapper for ReScript component to work with react_on_rails auto-registration
24
// react_on_rails looks for components in ror_components/ subdirectories
35

client/app/bundles/comments/startup/App/ror_components/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
import { Provider } from 'react-redux';
24
import React from 'react';
35
import ReactOnRails from 'react-on-rails-pro';

0 commit comments

Comments
 (0)