Pro RSC migration 3/3: React Server Components demo on webpack#729
Pro RSC migration 3/3: React Server Components demo on webpack#729ihabadham wants to merge 21 commits intoihabadham/feature/pro-rsc/basefrom
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 ( |
Code Review — PR #729: RSC demo on webpack (Sub-PR 3/3)OverviewSolid implementation of the RSC demo page. The webpack plumbing ( A few items worth addressing before merging: 🚨 Issues1.
2.
3. In
|
Without a local Suspense boundary, RSCRoute's in-flight fetch suspension bubbles up to whatever outer Suspense the Pro stream_react_component infrastructure provides. That outer fallback wipes the entire rendered tree, so during a Refresh or Simulate Error click, the whole page collapsed to viewport height for ~500ms and the browser snapped scrollY to 0 (since the page wasn't tall enough to preserve the prior scroll position). Verified empirically via window.scrollY + document.body.scrollHeight sampling on the deployed review-app: pre-fix, pageHeight went from 2193px to 764px between +50ms and +500ms after click. Local Suspense boundary contains the suspension to the LiveActivity section; same- shape skeleton fallback keeps the section's height stable so the surrounding layout doesn't shift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🎉 ✨ 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
|
Summary
Adds a React Server Components demo page at
/server-componentsvia the upstreamRSCWebpackPlugin(react-on-rails-rsc/WebpackPlugin), riding on top of Sub-PR 2's NodeRenderer + webpack setup.Part 3 of a stacked PR series. Targets
ihabadham/feature/pro-rsc/base, notmaster.References this PR follows
docs/oss/migrating/rsc-preparing-app.md(Steps 4-5),docs/pro/react-server-components/upgrading-existing-pro-app.md,docs/pro/react-server-components/how-react-server-components-work.md,docs/oss/migrating/rsc-component-patterns.mdreact_on_rails_pro/spec/dummy/config/webpack/rscWebpackConfig.js(RSC bundle shape, loader placement),spec/dummy/config/webpack/clientWebpackConfig.js(RSCWebpackPlugin({ isServer: false })pattern)react-on-rails-demo-marketplace-rsc/config/webpack/rscWebpackConfig.js(simpler resolve config without React path aliases),Procfile.dev(RSC_BUNDLE_ONLY=yeswatcher)server-components/demo components (ServerComponentsPage, ServerInfo, CommentsFeed, TogglePanel), routes + controller + viewChanges
Initializer
config/initializers/react_on_rails_pro.rb: addsenable_rsc_support = true,rsc_bundle_js_file = 'rsc-bundle.js',rsc_payload_generation_url_path = 'rsc_payload/'. On top of Sub-PR 2's NodeRenderer config.Webpack
config/webpack/clientWebpackConfig.js: addsRSCWebpackPlugin({ isServer: false, clientReferences: [...] })so the client bundle emitsreact-client-manifest.jsonwith client component chunk entries.config/webpack/rscWebpackConfig.js: new file. Derives fromserverWebpackConfig(true)via entry rename, adds thereact-serverresolve condition, aliasesreact-dom/server: false. Loader placement follows the Pro docs' prescribed pattern — handles both SWC (function-formrule.use) and Babel (array-formrule.use) transpilers.config/webpack/webpackConfig.js: addsRSC_BUNDLE_ONLYenv gate matching the existingSERVER_BUNDLE_ONLY/CLIENT_BUNDLE_ONLYpattern. Default build now compiles all three bundles (client + server + RSC).Components
'use client'directive added to 9 files per Pro docs' Step 5 rule (entry points using hooks,<Provider>, Redux, client APIs):App.jsx,NavigationBarApp.jsx,RouterApp.client.jsx,RouterApp.server.jsx(SSR wrapper — NOT a React Server Component despite the filename),SimpleCommentScreen.jsx,Footer.jsx,RescriptShow.jsx,stores-registration.js,stimulus-bundle.js.client/app/bundles/server-components/:ror_components/ServerComponentsPage.jsx— Server Component entry point (placed inror_components/so auto-bundling registers it viaregisterServerComponent).components/ServerInfo.jsx,components/CommentsFeed.jsx— Server Components.components/TogglePanel.jsx— Client Component ('use client') demonstrating the boundary.Routes / View
config/routes.rb: addsrsc_payload_route(Pro's RSC Flight endpoint) +get "server-components".app/controllers/pages_controller.rb:server_componentsaction.app/views/pages/server_components.html.erb:react_component("ServerComponentsPage", prerender: false, auto_load_bundle: true, trace: Rails.env.development?).auto_load_bundle: truepairs with auto-discovery — no manualregisterServerComponentcall needed.Dev / CI
Procfile.dev:wp-rscprocess runsRSC_BUNDLE_ONLY=yes bin/shakapacker --watchalongside existing client/server watchers.NavigationBar.jsx+paths.js: adds "RSC Demo" nav link to the new page.What we explicitly DON'T do (diverges from Justin's PR #723)
rspackRscPlugin.js(187 lines of custom rspack plugin). We use the upstreamreact-on-rails-rsc/WebpackPluginon webpack instead.client/app/packs/rsc-bundle.jsseparate source file. RSC bundle reusesserver-bundle.jsvia entry rename.client/app/packs/rsc-client-components.jsmanual side-import. Upstream plugin'sAsyncDependenciesBlockhandles client-ref chunk inclusion.registerServerComponentinstimulus-bundle.js. Auto-bundling'sreact_on_rails:generate_packsproduces the registration file (generated/ServerComponentsPage.jswithregisterServerComponent("ServerComponentsPage")) becauseServerComponentsPage.jsxlives in aror_components/directory.MessageChannelBannerPlugin polyfill. Webpack withtarget: 'node'(set in Sub-PR 2) avoids the underlying issue.Acceptance criteria
Manual QA on the deployed review app (
/deploy-review-appcomment in this PR). Sub-PR 2 validated its deploy via the same flow.GET /still returns 200 with full SSR content (existing pages unchanged)GET /server-componentsreturns 200 with RSC content streamed via Flight protocolGET /rsc_payload/ServerComponentsPagereturns valid Flight payload/server-componentshas no errors during page load and hydration[ReactOnRailsPro] Node Renderer respondedfor the RSC render pathServerComponentsPageStack context
react_on_rails_pro.RSCWebpackPlugin.shakapacker.ymlback to rspack onceshakacode/react_on_rails_rsc#29ships.