Pro RSC migration 3/3: React Server Components demo on webpack#729
Conversation
Add the three RSC fields per the marketplace demo initializer (react-on-rails-demo-marketplace-rsc/config/initializers/ react_on_rails_pro.rb): - enable_rsc_support = true - rsc_bundle_js_file = "rsc-bundle.js" - rsc_payload_generation_url_path = "rsc_payload/" Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RSCWebpackPlugin({ isServer: false }) on the client bundle scans for
'use client' files and adds them as entry points so they appear in the
client manifest (react-client-manifest.json). Without this, client
components referenced in RSC payloads wouldn't have matching chunks
in the client bundle.
clientReferences scoped to config.source_path, consistent with the
server bundle's scoping in serverWebpackConfig.js.
Reference: Pro dummy clientWebpackConfig.js:16-24.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Derives from serverWebpackConfig(true) — inherits target:'node', libraryTarget:'commonjs2', CSS filtering, and all server transforms. Adds three RSC-specific pieces: 1. RSC WebpackLoader pushed into the babel rule's use array (runs before babel per right-to-left order) to detect 'use client' directives in raw source and replace client exports with registerClientReference proxies. 2. react-server resolve condition so React's RSC-specific entry points are used. 3. react-dom/server aliased to false (RSC bundles generate Flight payloads, not HTML; importing react-dom/server causes a runtime error). Loader placement follows Pro dummy pattern (push into rule.use) per docs/oss/migrating/rsc-preparing-app.md:167-195. NOT marketplace's enforce:'post' which runs after transpilation and can miss directive AST nodes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add RSC_BUNDLE_ONLY env gate alongside the existing SERVER_BUNDLE_ONLY and CLIENT_BUNDLE_ONLY gates. Procfile.dev will use RSC_BUNDLE_ONLY=yes bin/shakapacker --watch to build the RSC bundle separately during development. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RSC auto-classification: files without 'use client' are registered as Server Components via registerServerComponent(). All existing components are Client Components (hooks, Redux, Router, event handlers), so they need the directive to preserve current behavior. Entry points (7 ror_components/ files): - App.jsx, NavigationBarApp.jsx, RouterApp.client.jsx, RouterApp.server.jsx (SSR wrapper, NOT a Server Component), SimpleCommentScreen.jsx, Footer.jsx, RescriptShow.jsx Pack entry files (2): - stores-registration.js, stimulus-bundle.js Per docs/oss/migrating/rsc-preparing-app.md Step 5 and docs/pro/react-server-components/create-without-ssr.md:52. Matches Justin's PR 723 final state exactly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- rsc_payload_route in routes.rb enables the Flight protocol endpoint for client-side RSC payload fetching. - get "server-components" route maps to pages#server_components. - View uses prerender: false (RSC components are streamed via the payload route, not traditional SSR prerender) and auto_load_bundle: false (ServerComponentsPage is not in ror_components/, so auto-discovery doesn't find its pack). - trace: Rails.env.development? gates server-timing headers to dev. Reference: Justin's PR 723 commits 4d09e13 + 0d8d75a. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server Components (no 'use client'):
- ServerComponentsPage.jsx: demo container showing RSC streaming
- components/ServerInfo.jsx: displays server environment info
- components/CommentsFeed.jsx: async data fetch with timeout,
env-gated delay, img sanitization, data.comments unwrap
Client Component ('use client'):
- components/TogglePanel.jsx: interactive panel demonstrating
'use client' boundary within a server component tree
Salvaged from Justin's PR 723 final state per the journey report
KEEP table. CommentsFeed specifically from commit f008295
(has the fetch timeout + sanitization fixes from review).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Procfile.dev: wp-rsc process runs RSC_BUNDLE_ONLY=yes bin/shakapacker --watch alongside existing client, server, and renderer processes. - paths.js: SERVER_COMPONENTS_PATH constant. - NavigationBar.jsx: "RSC Demo" link in the nav bar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The default branch (no env vars) runs during bin/shakapacker for production/CI builds. Without the RSC config in the array, the RSC bundle only gets built when RSC_BUNDLE_ONLY is set (dev watchers). Production deploys + CI would miss it. The *_BUNDLE_ONLY gates remain for dev Procfile processes (each watcher builds one bundle in isolation). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Justin's PR used manual registerServerComponent() in stimulus-bundle.js because his custom rspackRscPlugin didn't integrate with the auto-bundling flow. With the upstream RSCWebpackPlugin, auto-bundling works: the generate_packs task scans ror_components/ directories, classifies files without 'use client' as Server Components, and generates the registration file in generated/ServerComponentsPage.js automatically. Moved from: bundles/server-components/ServerComponentsPage.jsx Moved to: bundles/server-components/ror_components/ServerComponentsPage.jsx Updated relative imports (./components/ -> ../components/) and flipped the view from auto_load_bundle: false to true. No manual registration, no stimulus-bundle.js modification. Matches the Pro dummy pattern where server component sources sit in the auto-discovered directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous implementation only handled Array.isArray(rule.use) and only looked for babel-loader. The tutorial uses swc as its transpiler (shakapacker.yml: javascript_transpiler: swc), which makes Shakapacker generate rule.use as a FUNCTION, not an array. The RSC loader was therefore never attached to the transpilation rule — 'use client' files were left untransformed in the RSC bundle, producing 134 webpack warnings (export 'useState' not found in 'react' etc.) and setting up a runtime error when the RSC renderer would try to call client components directly instead of emitting client references. Follow the pattern from docs/oss/migrating/rsc-preparing-app.md:167 verbatim, which handles both forms and both loader names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
/deploy-review-app |
🚀 Quick Review App CommandsWelcome! Here are the commands you can use in this PR:
|
🎉 ✨ Deploy Complete! 🚀🌐 ➡️ Open Review AppDeployment successful for PR #729, commit 649e0bd 🎮 Control Plane Console |
Code Review — PR #729: RSC Demo via
|
| Severity | File | Issue |
|---|---|---|
| Medium | config/webpack/rscWebpackConfig.js:7–10 |
Missing entry-existence guard — silently assigns undefined as the RSC entry if server-bundle is absent; serverWebpackConfig.js throws a descriptive error for the same case |
| Low | client/app/bundles/server-components/components/CommentsFeed.jsx:90 |
allowedSchemes: ['https', 'http'] — http enables mixed-content image URLs in production; restrict to ['https'] |
| Low | client/app/bundles/server-components/components/ServerInfo.jsx:17 |
os.hostname() appears in the Flight payload and is therefore visible in raw HTTP responses; mask it outside development |
| Nit | app/views/pages/server_components.html.erb:5 |
Hardcoded id: is unusual — React on Rails auto-generates stable IDs, and the explicit value would produce a duplicate id if the partial is ever rendered more than once per page |
Observations (no action required, but worth noting)
'use client' in pack entry files (stimulus-bundle.js, stores-registration.js) — unusual placement since these are pack entry points, not component files. The commit message explains it correctly: without the directive, RSC auto-bundling would classify them as Server Components and break. The intent is clear; a short inline comment on each file would help future readers who don't have the PR history.
Default build now compiles three bundles — the final commit correctly includes the RSC config in the default webpackConfig.js array so CI/production builds emit rsc-bundle.js. This is the right call, but it adds a non-trivial build-time cost. Worth a note in the README or Procfile.dev comments so that local contributors who don't need RSC can understand how to skip it (RSC_BUNDLE_ONLY watcher aside, there's no NO_RSC_BUNDLE skip gate for the full build).
marked module-scope singleton — new Marked() + marked.use(gfmHeadingId()) at module scope is fine because gfmHeadingId() extension is stateless. Just flagging it was considered.
RouterApp.server.jsx gets 'use client' — the PR description explains this correctly (it's an SSR wrapper, not a React Server Component despite the filename). A one-line comment in the file noting this would prevent future confusion.
Summary
The core implementation is solid and follows the Pro docs pattern faithfully. The four inline comments above cover the notable gaps — the missing webpack entry guard is the most actionable fix before merge; the others are low/nit. No test coverage concerns given this is a demo page with manual QA acceptance criteria.
Greptile SummaryThis PR wires up the upstream Confidence Score: 5/5PR is safe to merge; all remaining findings are P2 style improvements. No P0 or P1 issues found. The webpack loader ordering is correct (right-to-left execution places the RSC loader before babel/swc), RSC bundle output path aligns with the initializer config, and the 'use client' directive placement follows the documented Pro pattern. The two P2 items (mixed-content http scheme and missing clearTimeout in error path) are minor style improvements that do not affect correctness of the demo. CommentsFeed.jsx — minor: allowedSchemes includes http, and clearTimeout is not called on fetch error paths. Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant Rails
participant NodeRenderer
participant RSCBundle as RSC Bundle (rsc-bundle.js)
participant RailsAPI as Rails API (/comments.json)
Browser->>Rails: GET /server-components
Rails->>Browser: HTML shell (react_component auto_load_bundle)
Browser->>Rails: GET /rsc_payload/ServerComponentsPage (Flight)
Rails->>NodeRenderer: Render RSC payload request
NodeRenderer->>RSCBundle: Execute ServerComponentsPage
RSCBundle->>RailsAPI: fetch /comments.json (server-side)
RailsAPI-->>RSCBundle: JSON comments
RSCBundle-->>NodeRenderer: React Flight payload (streamed)
NodeRenderer-->>Rails: Flight stream
Rails-->>Browser: RSC Flight payload
Browser->>Browser: Hydrate client components (TogglePanel)
Note over Browser: ServerInfo + CommentsFeed = pure HTML<br/>TogglePanel = hydrated JS island
Reviews (1): Last reviewed commit: "Wire RSC loader for both SWC and Babel t..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 649e0bdb48
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The renderer's stubTimers default of true replaces setTimeout with a no-op inside the VM. React's RSC server renderer uses setTimeout internally for Flight-protocol yielding, so stubbing it makes the RSC stream open without ever emitting a chunk. The request reaches the worker, the worker holds the accepted socket, but no data flows. Fastify eventually closes the idle connection at keepAliveTimeout (~72s), HTTPX retries once by its retries plugin, and Rails sees HTTPX::Connection::HTTP2::GoawayError after ~144s. Non-RSC SSR is unaffected because it doesn't rely on setTimeout for its async scheduling — only RSC's streaming path hits this. Verified by running a second renderer alongside on another port with RENDERER_STUB_TIMERS=false: the stuck path returned a full 9.7KB RSC payload for ServerComponentsPage in 422ms, vs. the default renderer timing out on the same request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this flag, console.* calls made inside async Server Components are captured by the renderer's per-request sharedConsoleHistory but not replayed back to Rails' logs. Any error-path logging from an async component (for example, a catch block that console.errors before returning an error fallback div) disappears, making runtime failures invisible. The generator template, RORP spec dummy, and every maintained RSC demo set this to true for the same reason. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🎉 ✨ Deploy Complete! 🚀🌐 ➡️ Open Review AppDeployment successful for PR #729, commit fd9faf1 🎮 Control Plane Console |
Code Review: Pro RSC migration 3/3This is a well-structured implementation of RSC on webpack. The PR description is thorough and the divergence from Justin's #723 approach (using upstream 🔴 Security
🟡 Performance / Build timeDefault build now compiles 3 bundles 🟡 Code clarity
🟢 Missing test coverageThe new Minor
Overall the webpack config plumbing ( |
🎉 ✨ Deploy Complete! 🚀🌐 ➡️ Open Review AppDeployment successful for PR #729, commit e2c76a5 🎮 Control Plane Console |
Code Review — PR #729: React Server Components demo (webpack)OverviewThis PR adds a well-structured RSC demo page at A few issues need attention before merging: 🔴 Must Fix1. System specs will silently pass without JavaScript ( The 2. refetchComponent(...)
.catch((err) => console.error('Retry refetch failed:', err))
.finally(() => resetErrorBoundary()); // always called
🟡 Should Fix3.
const rawHtml = await marked.parse(comment.text || '');This is safe since 4. Allowing 5. The 🟢 Minor / Informational6. Accessibility: The toggle 7. This is intentional for the review app, but the comment calls it "Off by default in production deployments" while the template ships it as 8. Test coverage gap: payload endpoint with bad component name
Webpack Config Notes (informational, not blocking)
|
Existing /rsc_payload/ServerComponentsPage spec sent empty {} props,
which only exercises the empty-state branch of CommentsFeed. The new
variant passes a realistic comment so the marked + sanitize-html
markdown rendering path and the comment list mapping are covered.
Verified locally: 4/4 examples pass against the running renderer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🎉 ✨ Deploy Complete! 🚀🌐 ➡️ Open Review AppDeployment successful for PR #729, commit 86eed7a 🎮 Control Plane Console |
Review: Pro RSC Migration 3/3 — React Server Components Demo on WebpackOverall: Well-structured addition. The RSC integration correctly follows upstream plugin patterns, server/client component boundaries are properly enforced with A few issues need attention before merge. Critical: The filename conventionally signals "React Server Component," but the directive marks it as a Client Component boundary. The PR description acknowledges this ("SSR wrapper — NOT a React Server Component despite the filename"), but the mismatch is a future maintenance hazard. Either rename the file or add a prominent comment explaining why it carries both signals. High: Stale closure in
High: This enables the 800 ms artificial delay in every review app deployment. Stakeholders checking unrelated PRs on the same app will see a slow page. Default to Medium: With no async extensions, Medium:
Medium:
Low:
Low: PR description says The actual code ( Nit: Request spec passes
|
|
✅ Review app for PR #729 was successfully deleted |
Style/TrailingCommaInArrayLiteral offense in the populated-comments RSC payload test. Didn't surface on #729 because lint_test.yml only triggers on PRs targeting master, so sub-PR CI skipped lint entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sub-PR workflows skip lint_test.yml (it triggers only on PRs targeting master), so these slipped through into the integration branch: - SimpleCommentScreen.jsx: pre-existing line-form disable for max-classes-per-file broke when #729 inserted `'use client'` ahead of it (the rule reports at file position 1:1; line-form disable on the import below no longer covered). Switch to a block-form disable at file top. - ServerComponentsPage.jsx, LiveActivity.jsx, CommentsFeed.jsx: block-form disable for react/prop-types. React 19 removed runtime propTypes validation; the new components rely on ES default destructuring rather than dead PropTypes declarations. - LiveActivityRefresher.jsx: line-form disable for react/no-unstable-nested-components on the ErrorBoundary's fallbackRender prop. The render-prop API is canonical for react-error-boundary when the fallback needs a closure; it cannot be hoisted without losing access to parent state (refreshKey, setRefreshKey, refetchComponent via buildRetry). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Components demo
Migrates this tutorial to React on Rails Pro with the Pro Node Renderer
for SSR, and adds a React Server Components demo at /server-components.
RSC is a Pro-only feature (`enable_rsc_support` doesn't exist in the OSS
gem), so demonstrating RSC inherently moves the whole app to Pro —
that's the entry ticket, not scope drift.
What changes:
- Dependencies: gem and npm move from react_on_rails to
react_on_rails_pro 16.6.0; React pinned to ~19.0.4 (RSC's minimum);
adds react-on-rails-pro-node-renderer and react-on-rails-rsc.
- SSR runtime: Server-side rendering moves from ExecJS to the Pro Node
Renderer (renderer/node-renderer.js, port 3800). The renderer wires
additionalContext: { URL, AbortController } for react-router-dom,
stubTimers: false so React's RSC server renderer can use setTimeout
for Flight-protocol yielding, and replayServerAsyncOperationLogs:
true to surface async-server-component console output.
- RSC demo: A new page at /server-components exercises React Server
Components end-to-end: server-only library imports (Node os +
lodash) that never reach the browser, async server components
streamed via <Suspense>, the donut pattern (a 'use client' component
nested in a server tree), and client-driven server-component
re-fetching with react-error-boundary + useRSC().refetchComponent.
- Bundler: shakapacker temporarily switched from rspack to webpack
while waiting for shakacode/react_on_rails_rsc#29 to ship rspack
support for the RSC plugin. Tracked by TODO in
config/shakapacker.yml.
Verification:
- Each sub-PR (#726, #728, #729) reviewed independently before
squashing into base.
- Manually QA'd on the deployed review app.
- Request and system specs cover the RSC payload endpoint and the
demo page interactions (spec/requests/server_components_spec.rb,
spec/system/server_components_demo_spec.rb).
- Lint, RSpec, and JS test workflows ran for the first time on the
base PR — sub-PRs target the base branch and skip those workflows
by trigger filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pro RSC migration 3/3: React Server Components demo
Adds a React Server Components demo page at
/server-components, riding on top of Sub-PR 2's NodeRenderer + webpack setup. Final sub-PR of the Pro RSC migration; targetsihabadham/feature/pro-rsc/base, notmaster.Demo
os,lodash) used to render a panel — none of those deps reach the browser.'use client'Client Component nested inside a Server-Component tree, hydrated normally.react-error-boundarycatching simulated errors and a Retry button that re-primes the cache before resetting the boundary.<Suspense>after the page shell.Verified
Deployed to the controlplane review app. All four sections render, RSC payloads stream, the Refresh / Simulate Error / Retry flow works end-to-end without page-shift artifacts. Covered by request + system specs (
spec/requests/server_components_spec.rb,spec/system/server_components_demo_spec.rb).Stack context
react_on_rails_pro(merged).master: consolidates all three sub-PRs; gets README + CLAUDE.md updates.config/shakapacker.ymlback to rspack once shakacode/react_on_rails_rsc#29 ships.References
rscWebpackConfig.js.