Skip to content

Conversation

@AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Oct 29, 2025

Summary

Solves #1886

Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Improved error handling for React Server Components with proper error serialization and logging to the server side.
  • Bug Fixes

    • Enhanced error propagation during server-side rendering to ensure errors are properly captured and displayed.
  • Tests

    • Added comprehensive test coverage for React Server Component error handling and serialization workflows.
  • Chores

    • Updated build and test configurations to support React Server Component testing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

This PR refactors error handling in React Server Components (RSC) rendering across packages by introducing RSC-specific error handlers, splitting error message generation from HTML rendering, and updating the streaming pipeline to pipe error streams through a transform rather than directly writing chunks. It also adds RSC-specific base modules and separates test execution between RSC and non-RSC tests.

Changes

Cohort / File(s) Summary
Error handling refactoring
packages/react-on-rails/src/generateRenderingErrorMessage.ts, packages/react-on-rails/src/handleError.ts, packages/react-on-rails-pro/src/handleError.ts, packages/react-on-rails-pro/src/handleErrorRSC.ts
Splits error handling: generateRenderingErrorMessage generates error message strings, handleError renders to HTML strings, and handleErrorRSC handles RSC-specific rendering via renderToPipeableStream. Removes HTML rendering from message generation.
Streaming pipeline refactoring
packages/react-on-rails-pro/src/streamingUtils.ts, packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts, packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
Adds handleError parameter to streamServerRenderedComponent signature; updates error handling to pipe error streams via pipeToTransform instead of using writeChunk and endStream; changes imports to use local error handlers.
RSC-specific base modules
packages/react-on-rails/src/base/full.rsc.ts, packages/react-on-rails/src/base/full.ts
Introduces full.rsc.ts for RSC bundle entry point with SSR-specific stubs; exports ReactOnRailsFullSpecificFunctions from full.ts; updates handleError import source.
Package exports and configuration
packages/react-on-rails/package.json, packages/react-on-rails-pro/jest.config.js, packages/react-on-rails-pro/package.json, knip.ts
Updates public exports to map handleError to new module, adds generateRenderingErrorMessage export, adds testEnvironmentOptions for NODE_CONDITIONS, splits test scripts into test:non-rsc and test:rsc, adds full.rsc.ts to knip entries.
RSC error propagation and testing
packages/react-on-rails-pro/src/RSCRoute.tsx, packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx, packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
Adds explicit error rethrow in PromiseWrapper for serialized errors; introduces tests for RSC payload serialization including Error objects and RSC bundle detection.
Build and utility updates
packages/react-on-rails/src/serverRenderReactComponent.ts, script/convert, CHANGELOG.md
Changes handleError import to use generateRenderingErrorMessage source; updates test script conversion logic; documents RSC error handling improvement.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RSCRoute as RSCRoute<br/>(PromiseWrapper)
    participant StreamPipeline as streamServerRendered<br/>ReactComponent
    participant ErrorHandler as handleError /<br/>handleErrorRSC
    participant Output as Output Stream

    Client->>RSCRoute: Render RSC
    RSCRoute->>StreamPipeline: streamServerRenderedComponent<br/>(options, renderStrategy, handleError)
    
    alt Success Path
        StreamPipeline->>Output: Pipe resolved stream
    else Error Path
        StreamPipeline->>ErrorHandler: Call handleError(options)
        ErrorHandler->>Output: Return error stream<br/>(HTML/RSC formatted)
        Output-->>StreamPipeline: Pipe via pipeToTransform
    end
    
    Output->>Client: Serialized response
    
    alt Error in Payload
        Client->>RSCRoute: createFromNodeStream<br/>(decodes payload)
        RSCRoute->>RSCRoute: Check if Error instance
        RSCRoute->>RSCRoute: Rethrow if Error
    else Success
        RSCRoute->>Client: Return resolved node
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Streaming pipeline refactoring: Changes to error handling paths in streamingUtils.ts and related streaming functions require careful review of the transform pipeline logic and error propagation.
  • Error handler split: Multiple new error handling modules across packages (handleError.ts, handleErrorRSC.ts, generateRenderingErrorMessage.ts) with different responsibilities need coordination verification.
  • Import path migrations: Several files update import sources for error handlers (ReactOnRailsRSC.ts, streamServerRenderedReactComponent.ts, serverRenderReactComponent.ts), requiring careful validation that all references resolve correctly.
  • Signature changes: streamServerRenderedComponent adds a third handleError parameter; verify all call sites pass this parameter correctly.

Possibly related PRs

Suggested reviewers

  • justin808
  • Judahmeek
  • alexeyr-ci2

Poem

🐰 Errors now flow through pipelines fair,
Where handleError handlers with care prepare.
RSC payloads dance in streams divine,
With split concerns—messages and renders align!
Tests in harmony, both RSC and not,
A rabbit's delight at this refactoring plot! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Don't include 'react-dom/server' in the RSC bundle" directly aligns with the primary objective stated in the PR description: removing react-dom/server from the RSC bundle. The changeset supports this goal through multiple mechanisms, including conditional exports in package.json (with separate "react-server" entry points), RSC-specific implementations (full.rsc.ts, handleErrorRSC.ts), updated Jest configuration with NODE_CONDITIONS support, and new RSC test scripts. The title is specific, clear, and concise—it unambiguously conveys what the PR accomplishes without unnecessary vagueness or noise. A developer scanning git history would immediately understand this change prevents a dependency from appearing in a specific bundle type.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch abanoubghadban/remove-react-dom-server-from-rsc-bundle

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added the bug label Oct 29, 2025
@claude
Copy link

claude bot commented Oct 29, 2025

PR Review: Remove react-dom/server from RSC Bundle

Great work on addressing issue #1886! This PR successfully eliminates react-dom/server dependencies from the RSC bundle, which is crucial for proper React Server Components support. Here's my comprehensive review:


Strengths

  1. Clean Architecture & Separation of Concerns

    • Excellent refactoring of error handling into separate handleError.ts and handleErrorRSC.ts files
    • The new streamingUtils.ts properly centralizes shared streaming logic
    • Clear separation between regular SSR and RSC rendering paths
  2. Smart Error Message Extraction

    • Extracting generateRenderingErrorMessage into a standalone utility (generateRenderingErrorMessage.ts:1-66) is a great design decision
    • This allows both SSR and RSC bundles to share error message logic without importing react-dom/server
  3. Proper Package.json Export Conditions

    • The conditional export for "./@internal/base/full" using "react-server" condition is exactly the right approach:
      "./@internal/base/full": {
        "react-server": "./lib/base/full.rsc.js",
        "default": "./lib/base/full.js"
      }
    • This ensures the correct bundle is loaded based on the environment
  4. Comprehensive Test Coverage

    • The new test ReactOnRailsRSC.test.tsx:1-13 explicitly validates that importing the RSC bundle doesn't trigger react-dom/server imports
    • Existing streaming tests appear comprehensive and should catch regressions
  5. Consistent Error Handling Pattern

    • Both handleError.ts and handleErrorRSC.ts follow the same interface, differing only in implementation details (which renderer to use)

🔍 Potential Issues & Suggestions

1. Missing Required Checklist Items ⚠️

The PR checklist items are unchecked:

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Action Required:

2. Incomplete Error Handling in full.rsc.ts 🐛

In packages/react-on-rails/src/base/full.rsc.ts:16-22, the stub methods throw errors:

const reactOnRailsFullSpecificFunctions: ReactOnRailsFullSpecificFunctions = {
  handleError() {
    throw new Error('"handleError" function is not supported in RSC bundle');
  },
  serverRenderReactComponent() {
    throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
  },
};

Concern: If any code path accidentally calls these in an RSC context, it will throw a runtime error instead of being caught at build/type time.

Suggestion: Consider:

  • Adding JSDoc comments explaining why these throw
  • Or making these type-level only (TypeScript won't allow calling them)

3. Hardcoded RSC Options in handleErrorRSC.ts 🤔

In packages/react-on-rails-pro/src/handleErrorRSC.ts:9-11:

return renderToPipeableStream(reactElement, {
  filePathToModuleMetadata: {},
  moduleLoading: { prefix: '', crossOrigin: null },
});

Question: Are these hardcoded empty values always correct for error rendering? Should these come from the railsContext or configuration?

Suggestion: Add a comment explaining why empty values are safe here, or consider passing these as parameters if they could vary.

4. Test Environment Configuration 📝

In jest.config.js:31-33, there's a new test environment option:

testEnvironmentOptions: {
  customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? [],
},

Suggestion: Document when/why NODE_CONDITIONS should be set. This appears to be for testing the react-server export condition, but it's not obvious from the code.

5. Potential Type Safety Improvement 💡

The streamServerRenderedComponent function now takes a handleError parameter (streamingUtils.ts:180), but it's typed as returning PipeableOrReadableStream.

Suggestion: Consider creating a more specific type for the error handler to make it clearer what the contract is:

type ErrorHandler = (options: ErrorOptions) => PipeableOrReadableStream;

6. Missing Error Tests for RSC-Specific Errors 🧪

The new ReactOnRailsRSC.test.tsx only tests successful imports. Consider adding tests for:

  • Error rendering in RSC context
  • Ensuring error HTML doesn't import react-dom/server
  • Validating the RSC-specific renderToPipeableStream options

🔒 Security Considerations

✅ No security concerns identified:

  • Error messages are generated server-side and don't expose sensitive data
  • No user input is processed unsafely
  • The streaming implementation maintains existing security boundaries

Performance Considerations

Positive impact:

  • Removing react-dom/server from the RSC bundle reduces bundle size
  • Streaming logic centralization may improve maintainability without performance regression
  • Existing streaming tests show the performance characteristics are maintained

📋 Code Quality & Best Practices

Excellent adherence to project standards:

  • ✅ Follows TypeScript best practices with proper typing
  • ✅ Maintains existing code structure and patterns
  • ✅ Clear separation of concerns
  • ✅ Comprehensive JSDoc comments on complex functions like bufferStream

Per CLAUDE.md requirements:

  • ⚠️ Ensure you run bundle exec rubocop before final commit
  • ⚠️ Verify all files end with newline characters
  • ⚠️ Consider running rake autofix for formatting consistency

📊 Summary

Overall Assessment:Approve with minor suggestions

This is a well-architected solution that properly separates RSC and SSR concerns. The code quality is high, and the approach is sound. The main action items are:

  1. Must do before merge:

    • Add CHANGELOG entry
    • Run bundle exec rubocop and fix any violations
    • Verify trailing newlines on all files
  2. Nice to have:

    • Document the NODE_CONDITIONS environment variable usage
    • Add comment explaining hardcoded RSC options in handleErrorRSC.ts
    • Consider adding more RSC-specific error handling tests

Great work on this refactoring! The separation of error handling logic while maintaining the same interface is particularly well done.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)

64-76: Notify post‑SSR hooks (and optionally flag error) in the string fallback path.

Currently, when a render function returns a string, we end the stream without calling post‑SSR hooks; hooks relying on notifySSREnd never fire. Optionally, flag this as an error to surface misuse.

Apply:

-        writeChunk(reactRenderedElement);
-        endStream();
+        // Mark misuse for diagnostics and still stream the string
+        reportError(
+          new Error(
+            `stream_react_component received a string for "${componentName}". Expected a React element.`
+          ),
+        );
+        writeChunk(reactRenderedElement);
+        // Ensure post-SSR hooks run even in this fallback
+        streamingTrackers.postSSRHookTracker.notifySSREnd();
+        endStream();
         return;
🧹 Nitpick comments (6)
packages/react-on-rails/src/base/full.rsc.ts (1)

6-38: Consider extracting common logic to reduce duplication.

The createBaseFullObject function is nearly identical to the one in full.ts (lines 34-66), differing only in the stub implementations. While this duplication may be intentional to avoid importing SSR modules in the RSC bundle, consider whether the common scaffolding logic could be extracted to reduce maintenance burden.

However, if the duplication is necessary for proper tree-shaking and bundle separation, this is acceptable.

packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)

78-93: Abort React stream on shell error to avoid orphaned work.

If the shell fails, we never pipe the React stream; abort it to free resources and prevent background work.

-        onShellError(e) {
-          sendErrorHtml(convertToError(e));
-        },
+        onShellError(e) {
+          try {
+            // Stop any ongoing React work
+            renderingStream.abort();
+          } catch {}
+          sendErrorHtml(convertToError(e));
+        },
packages/react-on-rails-pro/src/streamingUtils.ts (4)

146-151: Fix outdated comment (we’re not using Readable.from here).

Comment mentions Readable.from, but code wraps via bufferStream. Update for accuracy.

-  // 3. By wrapping in Readable.from(), we can explicitly emit errors into the readableStream without affecting the transformStream
-  // Note: Readable.from can merge multiple chunks into a single chunk, so we need to ensure that we can separate them later
+  // 3. By wrapping the PassThrough in bufferStream(), we can explicitly emit errors into the returned readable
+  //    without affecting the underlying transformStream. bufferStream also preserves event order before reading starts.

156-158: Harden abort guard to avoid calling a non‑function.

Use a function check rather than property existence.

-    if (pipedStream && 'abort' in pipedStream) {
-      pipedStream.abort();
-    }
+    if (pipedStream && typeof (pipedStream as any).abort === 'function') {
+      (pipedStream as any).abort();
+    }

217-219: Clarify error message for server render hash in streaming path.

Improve wording for faster diagnosis.

-      throw new Error('Server rendering of streams is not supported for server render hashes.');
+      throw new Error(
+        'Streaming is not supported when the render function returns a server render hash. ' +
+          'Register the component (not a renderer) or return a React element.'
+      );

236-250: RSC error‑handling alignment (context check).

Centralized error streaming via handleError is good. Ensure the RSC path passes an RSC‑specific handleErrorRSC so we don’t over‑eagerly render HTML for RSC payload errors; RSC fetchers should surface errors to client error boundaries. Based on learnings.

Can you confirm streamServerRenderedComponent is invoked with handleErrorRSC for RSC entry points and with handleError for SSR?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e02c7a and 413e5d0.

📒 Files selected for processing (12)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (4 hunks)
  • packages/react-on-rails-pro/src/handleError.ts (1 hunks)
  • packages/react-on-rails-pro/src/handleErrorRSC.ts (1 hunks)
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (3 hunks)
  • packages/react-on-rails-pro/src/streamingUtils.ts (1 hunks)
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx (1 hunks)
  • packages/react-on-rails/package.json (1 hunks)
  • packages/react-on-rails/src/base/full.rsc.ts (1 hunks)
  • packages/react-on-rails/src/base/full.ts (1 hunks)
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts (1 hunks)
  • packages/react-on-rails/src/handleError.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails/src/base/full.rsc.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails/package.json
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails/src/base/full.rsc.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • packages/react-on-rails/package.json
🧬 Code graph analysis (9)
packages/react-on-rails-pro/jest.config.js (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
packages/react-on-rails-pro/src/handleError.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToPipeableStream (4-4)
packages/react-on-rails/src/base/full.rsc.ts (2)
packages/react-on-rails/src/base/full.ts (3)
  • createBaseFullObject (34-66)
  • BaseFullObjectType (29-32)
  • ReactOnRailsFullSpecificFunctions (19-22)
packages/react-on-rails/src/base/client.ts (2)
  • createBaseClientObject (58-275)
  • BaseClientObjectType (43-52)
packages/react-on-rails/src/generateRenderingErrorMessage.ts (1)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/handleError.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToString (4-4)
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)
packages/react-on-rails-pro/src/streamingUtils.ts (1)
  • streamServerRenderedComponent (179-251)
packages/react-on-rails-pro/src/handleErrorRSC.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToPipeableStream (4-4)
packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (2)
packages/react-on-rails-pro/src/streamingUtils.ts (1)
  • streamServerRenderedComponent (179-251)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails-pro/src/streamingUtils.ts (4)
packages/react-on-rails/src/types/index.ts (5)
  • RenderParams (220-224)
  • StreamRenderState (473-476)
  • ErrorOptions (236-243)
  • assertRailsContextWithServerComponentMetadata (80-94)
  • RailsContextWithServerStreamingCapabilities (65-68)
packages/react-on-rails/src/serverRenderUtils.ts (3)
  • createResultObject (9-24)
  • validateComponent (30-36)
  • convertToError (26-28)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/isServerRenderResult.ts (1)
  • isServerRenderHash (8-17)
🪛 GitHub Actions: JS unit tests for Renderer package
packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx

[error] 6-9: The 'react' package in this environment is not configured correctly. The 'react-server' condition must be enabled for React Server Components. This caused the test import to fail.

🪛 GitHub Actions: Lint JS and Ruby
packages/react-on-rails/src/base/full.ts

[warning] 19-19: Unused exported type: ReactOnRailsFullSpecificFunctions.

packages/react-on-rails/src/base/full.rsc.ts

[warning] 1-1: Unused files detected by knip: full.rsc.ts.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: claude-review
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: lint-js-and-ruby
🔇 Additional comments (16)
packages/react-on-rails/src/base/full.ts (1)

19-22: LGTM! Export is necessary for RSC implementation.

The export of ReactOnRailsFullSpecificFunctions is used in full.rsc.ts (line 2), ensuring type safety across both the standard and RSC-specific implementations. The pipeline warning appears to be a false positive.

packages/react-on-rails/src/base/full.rsc.ts (2)

1-4: LGTM! Type re-exports maintain API compatibility.

The imports and type re-exports ensure the RSC bundle exposes the same type interface as the standard bundle, maintaining consistency across implementations.


1-38: I need to verify the build configuration to ensure full.rsc.ts is actually being compiled to the output location specified in package.json.

Now I need to check the actual build configuration to verify the TypeScript file is being compiled:

Based on my verification of the package.json exports and build configuration patterns, I can now provide a final assessment:

RSC bundle configuration is properly set up; knip warning does not indicate a build system problem.

The package.json correctly exports ./@internal/base/full with a "react-server" condition mapping to ./lib/base/full.rsc.js. This is the correct and standard pattern for React Server Components bundles. Files are reported as unused if they are in the set of project files, but not in the set of files resolved from the entry files, which explains the knip warning—but this doesn't reflect an actual build system issue.

Entry point source files (like full.rsc.ts) are typically not listed in knip's entry file configuration. The build system should compile this file to the output location specified in the package.json exports. The warning is likely a false positive; if the build process hasn't been run yet on this PR branch, knip won't resolve the compiled output file.

No action is required for the code or configuration shown. Verify the build pipeline runs successfully to confirm full.rsc.ts compiles to lib/base/full.rsc.js, and the knip warning should resolve once the build artifacts are present.

packages/react-on-rails/src/handleError.ts (1)

4-9: LGTM! Clean separation of concerns.

The refactoring delegates error message generation to a dedicated module and renders it consistently. The use of renderToString is appropriate for synchronous string-based error rendering in the non-RSC bundle.

packages/react-on-rails-pro/src/handleError.ts (1)

1-12: LGTM! Correct streaming implementation for Pro package.

The implementation correctly uses renderToPipeableStream for async streaming error rendering, while maintaining the same error message generation logic through the shared generateRenderingErrorMessage module.

packages/react-on-rails/package.json (2)

50-50: LGTM! New export enables shared error message generation.

The new export for generateRenderingErrorMessage allows both the base and Pro packages to share error formatting logic.


56-59: LGTM! Conditional exports correctly separate RSC and default bundles.

The conditional export structure ensures that:

  • The react-server condition loads the RSC-specific implementation (full.rsc.js) which avoids importing react-dom/server
  • The default condition loads the standard implementation (full.js) with full server rendering capabilities

This change directly addresses the PR objective of removing react-dom-server from the RSC bundle.

packages/react-on-rails-pro/src/handleErrorRSC.ts (1)

1-15: LGTM! RSC-specific error handler correctly avoids react-dom/server.

The implementation correctly uses react-on-rails-rsc/server.node for rendering, which aligns with the PR objective. The additional RSC-specific options (filePathToModuleMetadata and moduleLoading) are provided with sensible defaults for error rendering scenarios.

packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (4)

26-26: LGTM! Correctly imports RSC-specific error handler.

The import of handleError from ./handleErrorRSC.ts ensures the RSC bundle uses the RSC-specific rendering implementation that doesn't depend on react-dom/server.


54-55: LGTM! Refactored to use centralized streaming utilities.

The use of pipeToTransform from the new streaming utilities provides consistent error handling and stream transformation across the codebase.


86-91: LGTM! Error handling correctly uses RSC-specific renderer.

The error path now uses handleError (from handleErrorRSC.ts) to generate error HTML and pipes it through the transform stream, maintaining consistency with the new streaming architecture.


99-105: LGTM! Passes RSC error handler to streaming component.

The handleError parameter enables streamServerRenderedComponent to use the RSC-specific error rendering path, completing the separation of RSC and standard server rendering.

packages/react-on-rails/src/generateRenderingErrorMessage.ts (2)

3-33: LGTM! Clear error detection for render function issues.

The helper function correctly identifies and logs common render function misconfiguration patterns, providing helpful error messages to developers.


35-64: Verify the client-side return value.

The function returns the string literal 'undefined' for client-side rendering (line 63). This seems unusual - typically you'd return undefined (the value) or an empty string.

Check if callers expect the string 'undefined' or if this should be:

return undefined; // or return '';

Looking at the callers (handleError.ts and handleError.ts in Pro), they both wrap the result in a React element and render it, which suggests they're always called server-side. If that's the case, the client-side branch may be unreachable, but clarification would be helpful.

packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx (1)

1-13: Fix the pipeline failure by adding the react-server export condition.

The test correctly validates that ReactOnRailsRSC doesn't import react-dom/server. However, the pipeline failure indicates the test environment needs the 'react-server' export condition enabled.

Set the NODE_CONDITIONS environment variable in your test script to include 'react-server':

#!/bin/bash
# Add this to your package.json test script or CI configuration
NODE_CONDITIONS=react-server yarn test

Alternatively, update the jest.config.js to include 'react-server' by default:

   testEnvironmentOptions: {
-    customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? [],
+    customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? ['react-server'],
   },
⛔ Skipped due to learnings
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)

17-17: The code structure properly separates ReactDOMServer from RSC builds. Review comment is incorrect.

The separation is guaranteed by:

  1. Source isolation: streamServerRenderedReactComponent.ts (which imports ReactDOMServer) is imported exclusively in ReactOnRails.node.ts, never in RSC entry points.

  2. Entry point isolation: ReactOnRailsRSC.ts (RSC entry) does not import streamServerRenderedReactComponent or ReactOnRails.node.ts—only RSC-specific files like handleErrorRSC.ts and the external react-on-rails-rsc package.

  3. Conditional exports: The package exports define separate conditions (react-serverReactOnRailsRSC.js, nodeReactOnRails.node.js), and ReactDOMServer has no react-server condition, so tree-shaking works correctly.

  4. No cross-contamination: RSC-specific files (.rsc.ts, .rsc.tsx) do not import from node or SSR modules.

No additional verification needed—the current design already prevents ReactDOMServer from being bundled into RSC builds.

Likely an incorrect or invalid review comment.

Comment on lines 31 to 33
testEnvironmentOptions: {
customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? [],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consider defaulting to 'react-server' export condition.

The current implementation defaults to an empty array, which causes the pipeline test failure. Since the RSC tests require the 'react-server' condition, consider including it by default.

Apply this diff:

   testEnvironmentOptions: {
-    customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? [],
+    customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? ['react-server'],
   },

This ensures RSC tests work correctly without requiring manual environment variable configuration.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
testEnvironmentOptions: {
customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? [],
},
testEnvironmentOptions: {
customExportConditions: process.env.NODE_CONDITIONS?.split(',') ?? ['react-server'],
},
🤖 Prompt for AI Agents
In packages/react-on-rails-pro/jest.config.js around lines 31 to 33, the
testEnvironmentOptions.customExportConditions currently defaults to an empty
array which breaks RSC tests; change the logic so that when NODE_CONDITIONS is
unset or empty it defaults to ['react-server'], and when NODE_CONDITIONS is set
include its values but ensure 'react-server' is present (and deduplicated) in
the resulting array before assigning it to customExportConditions.

Comment on lines +15 to +23
const reactOnRailsFullSpecificFunctions: ReactOnRailsFullSpecificFunctions = {
handleError() {
throw new Error('"handleError" function is not supported in RSC bundle');
},

serverRenderReactComponent() {
throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix type signature mismatch in stub functions.

The stub implementations don't match the expected signatures from ReactOnRailsFullSpecificFunctions. While the functions throw errors immediately, they must still satisfy the type contract to maintain type safety.

Apply this diff to correct the function signatures:

   const reactOnRailsFullSpecificFunctions: ReactOnRailsFullSpecificFunctions = {
-    handleError() {
+    handleError(options) {
+      void options; // Mark as used
       throw new Error('"handleError" function is not supported in RSC bundle');
     },
 
-    serverRenderReactComponent() {
+    serverRenderReactComponent(options) {
+      void options; // Mark as used
       throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
     },
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const reactOnRailsFullSpecificFunctions: ReactOnRailsFullSpecificFunctions = {
handleError() {
throw new Error('"handleError" function is not supported in RSC bundle');
},
serverRenderReactComponent() {
throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
},
};
const reactOnRailsFullSpecificFunctions: ReactOnRailsFullSpecificFunctions = {
handleError(options) {
void options; // Mark as used
throw new Error('"handleError" function is not supported in RSC bundle');
},
serverRenderReactComponent(options) {
void options; // Mark as used
throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
},
};
🤖 Prompt for AI Agents
In packages/react-on-rails/src/base/full.rsc.ts around lines 15 to 23, the stub
functions throw errors but do not match the parameter and return type signatures
defined by ReactOnRailsFullSpecificFunctions; update each stub to use the exact
parameter list and return type declared in the ReactOnRailsFullSpecificFunctions
interface (e.g. add the same parameters and return type annotations) and keep
the immediate throw inside the body so the implementation still throws an Error
indicating the function isn't supported in the RSC bundle.

@claude
Copy link

claude bot commented Oct 29, 2025

Code Review - PR #1888: Don't include react dom server at the rsc bundle

Summary

This PR addresses issue #1886 by separating ReactDOMServer dependencies from the RSC bundle, improving error handling for React Server Components. The changes refactor error handling to use appropriate rendering methods for RSC vs traditional SSR.


Strengths

1. Excellent Architecture & Separation of Concerns

  • Clean module separation: Created dedicated handleError.ts and handleErrorRSC.ts files, each using the appropriate rendering method
  • Proper conditional exports: The package.json exports map correctly routes RSC builds to full.rsc.js via the react-server condition
  • DRY principle: Extracted generateRenderingErrorMessage to eliminate duplication between error handlers

2. Smart Error Propagation in RSC

  • RSCRoute.tsx:80-87: Clever handling of serialized errors in RSC payloads - catches errors embedded in the payload and re-throws them for proper error boundaries
  • This solves the issue where RSC rendering errors weren't showing proper stack traces

3. Robust Streaming Pipeline

  • streamingUtils.ts: Well-documented bufferStream function with clear comments explaining error handling and event ordering
  • Consistent error handling pattern: both handlers now use pipeToTransform instead of mixing writeChunk/endStream

4. Good Test Coverage

  • ReactOnRailsRSC.test.tsx: Excellent verification that react-dom/server isn't imported in RSC bundle (mocking it to throw ensures import failures)

⚠️ Issues & Concerns

CRITICAL: Missing File Extension in Import

Location: packages/react-on-rails-pro/src/ReactOnRailsRSC.ts:26

import handleError from './handleErrorRSC.ts';

Issue: The .ts extension should be .js for runtime imports. TypeScript source uses .ts, but compiled output is .js. This will cause runtime errors.

Fix: Change to:

import handleError from './handleErrorRSC.js';

Same issue in streamServerRenderedReactComponent.ts:30:

import handleError from './handleError.ts';  // Should be .js

IMPORTANT: Pull Request Checklist Not Completed

The PR description shows the checklist is incomplete:

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Required Actions:

  1. Tests: While you added ReactOnRailsRSC.test.tsx (great!), consider adding integration tests that verify error rendering works correctly for both RSC and traditional SSR
  2. Documentation: Update docs to explain the RSC error handling behavior, especially the serialized error pattern in RSCRoute.tsx
  3. CHANGELOG: Add entry describing this fix (per CLAUDE.md: "Add the CHANGELOG entry at the top of the file")

🔍 Code Quality Observations

1. Type Safety

  • Good use of ErrorOptions type across all error handlers
  • The full.rsc.ts type assertions are well-documented (lines 25-30)

2. Consistent Error Handling Pattern

Before:

writeChunk(errorHtml);
endStream();

After:

pipeToTransform(errorHtmlStream);

This unifies the pattern across both RSC and traditional rendering.

3. Test Environment Configuration

jest.config.js:31-33 correctly adds customExportConditions to support the react-server condition during tests.


🎯 Suggestions

1. Error Message Consistency

In handleErrorRSC.ts:7, you're passing new Error(msg) to renderToPipeableStream. Consider whether this provides the same error details as the traditional <pre> element approach. The RSC payload might serialize this differently.

Question: Have you tested that error stack traces are visible in the browser with this approach?

2. Missing Context in handleErrorRSC

The renderToPipeableStream options seem minimal:

{
  filePathToModuleMetadata: {},
  moduleLoading: { prefix: '', crossOrigin: null },
}

Are these the correct defaults? Should they be configurable or pulled from the options?

3. Type Definition Location

streamingUtils.ts:38-43: The StreamingTrackers type was moved here. Ensure this is exported from an appropriate index file if other modules need it.


🐛 Potential Bugs

1. Error Boundary Behavior (Low severity)

In RSCRoute.tsx:84, you check promiseResult instanceof Error.

Concern: Serialized errors through RSC payloads might not maintain instanceof Error after deserialization. Consider also checking for error-like shapes:

if (promiseResult instanceof Error || (promiseResult && typeof promiseResult === 'object' && 'message' in promiseResult && 'stack' in promiseResult)) {
  throw promiseResult;
}

2. Resource Cleanup

In ReactOnRailsRSC.ts:90, you removed endStream() call. Verify that pipeToTransform properly ends the stream in all error cases.


🔒 Security

No security concerns identified. Error messages are already sanitized through generateRenderingErrorMessage.


Performance

Positive Impact: Removing react-dom/server from RSC bundle will reduce bundle size and avoid unnecessary dependencies in the server component context.


📋 Before Merging

Must Fix:

  1. ❗ Change .ts to .js in import statements (runtime imports)
  2. ❗ Complete PR checklist (tests, docs, CHANGELOG)

Should Consider:
3. Test error serialization behavior in RSC payloads
4. Add integration tests for error scenarios
5. Verify stream cleanup in error paths

Before committing:

  • Run bundle exec rubocop (per CLAUDE.md requirements)
  • Run rake autofix to ensure formatting
  • Ensure all files end with newlines

🎉 Overall Assessment

This is a well-structured refactoring that properly separates concerns between RSC and traditional SSR. The core architecture is solid, but the import path issues and incomplete PR checklist need attention before merging.

Recommendation: Request changes for the import paths, then approve after addressing checklist items.


Great work on the error handling improvements! The serialized error detection in RSCRoute.tsx is particularly elegant. 🚀

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/react-on-rails-pro/src/RSCRoute.tsx (1)

78-89: Consider refining the promise type to include Error.

Since the code explicitly checks for Error as a resolved value (line 84), the promise parameter type could be refined to Promise<React.ReactNode | Error> for better type accuracy. However, this may be intentionally omitted to keep the external API clean, so this is optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 413e5d0 and 6cf9c7c.

📒 Files selected for processing (2)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • packages/react-on-rails-pro/src/handleErrorRSC.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
🧠 Learnings (3)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: lint-js-and-ruby
🔇 Additional comments (1)
packages/react-on-rails-pro/src/RSCRoute.tsx (1)

84-86: The review comment is incorrect based on how React Server Components handle error serialization.

The concern assumes plain JSON serialization loses the Error prototype. However, React RSC uses a custom "Flight" serialization protocol (not JSON) that explicitly supports Error objects. The code comment itself documents this: "RSC bundle will return an error object serialized inside the RSC payload."

Key evidence:

  • React's Flight protocol has built-in support for error serialization (per React's official documentation and GitHub discussions)
  • The instanceof Error check is the standard pattern for RSC error handling
  • React.use() properly deserializes errors from RSC payloads, preserving their type
  • The codebase's convertToError() utility already handles both Error and non-Error thrown values appropriately

The existing code is correct and requires no changes.

Likely an incorrect or invalid review comment.

@claude
Copy link

claude bot commented Oct 29, 2025

PR Review: Remove react-dom/server from RSC Bundle

Summary

This PR addresses issue #1886 by removing react-dom/server dependencies from the RSC (React Server Components) bundle to improve error reporting. The changes introduce proper separation between streaming SSR and RSC error handling, implement conditional package exports, and add testing infrastructure.


Strengths

1. Excellent Architecture & Separation of Concerns

  • Separate error handlers: Creating handleError.ts (SSR) and handleErrorRSC.ts (RSC) is the right approach for different rendering contexts
  • New streamingUtils.ts: Good refactoring to consolidate streaming logic and reduce file size
  • Conditional exports: Using "react-server" condition in package.json exports is the correct way to provide different bundles for RSC vs SSR contexts

2. Improved Error Handling

  • Error propagation in RSCRoute.tsx (lines 82-86): Good addition to check if promiseResult is an Error and throw it properly - this ensures errors in RSC payloads are caught
  • Consistent stream-based errors: Converting error handling to use pipeToTransform instead of writeChunk/endStream provides cleaner error propagation
  • Structured error responses: The refactoring ensures errors are properly formatted and streamed rather than truncated

3. Test Coverage

  • RSC-specific test: The new test in ReactOnRailsRSC.test.tsx is clever - it mocks react-dom/server to throw if imported, ensuring the RSC bundle is truly independent
  • Conditional test execution: Using NODE_CONDITIONS=react-server for the RSC test is proper Node.js export condition handling
  • Jest configuration: The testEnvironmentOptions configuration properly supports custom export conditions

⚠️ Issues & Concerns

1. Critical: Missing Newlines 🚨

Per CLAUDE.md requirements, ALL files MUST end with a newline character. Please verify these files end with newlines:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts

Action required: Run bundle exec rubocop before committing (per CLAUDE.md).

2. Typo in Comment (Minor)

File: packages/react-on-rails-pro/src/RSCRoute.tsx:83

// RSC bundle will return an error object serilaized inside the RSC payload

Should be "serialized" not "serilaized"

3. Missing .ts Extension Warning

Several imports use .ts extensions:

  • handleErrorRSC.ts import in ReactOnRailsRSC.ts:26
  • streamingUtils.ts import in multiple files
  • client.ts, full.ts in full.rsc.ts

While TypeScript can handle this, it's worth verifying the build output is correct. The .ts extensions might need to be .js in the compiled output.

4. handleError Return Type Inconsistency

  • SSR handleError: Returns Readable.from([htmlString]) - single-chunk stream
  • RSC handleErrorRSC: Returns renderToPipeableStream(new Error(msg), {...}) - pipeable stream with metadata

Both implement PipeableOrReadableStream, but the different approaches could lead to subtle behavior differences. Consider documenting why each approach is used.

5. Test Coverage Gaps

The new test only verifies that the RSC bundle can be imported without importing react-dom/server. Consider adding:

  • Tests for error scenarios in RSC rendering
  • Tests for the new error handlers (handleError.ts, handleErrorRSC.ts)
  • Integration tests for streaming error propagation
  • Tests verifying error messages are properly formatted

6. Double Negation in Jest Config

testEnvironmentOptions: !!process.env.NODE_CONDITIONS ? {
  customExportConditions: process.env.NODE_CONDITIONS.split(','),
} : {},

The !! is redundant here. Could simplify to:

testEnvironmentOptions: process.env.NODE_CONDITIONS ? {
  customExportConditions: process.env.NODE_CONDITIONS.split(','),
} : {},

7. Error Object in RSC Payload

File: RSCRoute.tsx:82-86
The comment says "RSC bundle will return an error object serialized inside the RSC payload", but the code checks instanceof Error. Consider:

  • Will deserialized errors still be instanceof Error?
  • Should you check error properties instead (e.g., promiseResult?.name === 'Error')?
  • Add a test case to verify this works with actual serialized errors

🔒 Security Considerations

✅ No Major Concerns

  • Error messages are generated through existing generateRenderingErrorMessage function
  • No new user input handling or injection points
  • Stream handling appears safe

⚠️ Minor: Error Message Exposure

Ensure generateRenderingErrorMessage doesn't expose sensitive information (stack traces, file paths) in production. This is likely already handled but worth verifying.


Performance Considerations

✅ Positive Impact

  • Smaller RSC bundle: Removing react-dom/server will reduce bundle size for RSC contexts
  • Better streaming: Consolidated error handling through pipeToTransform should be more efficient

Neutral

  • New file structure shouldn't impact runtime performance
  • Conditional exports have negligible overhead

📋 PR Checklist Reminders

From the PR description, these items are still unchecked:

  • Add/update test to cover these changes - Partially done, but needs more coverage
  • Update documentation - No docs updated yet
  • Update CHANGELOG file - Not done yet

🎯 Recommendations

High Priority

  1. Run bundle exec rubocop and ensure all files end with newlines
  2. Fix the typo: "serilaized" → "serialized"
  3. Add tests for error handling scenarios
  4. Update CHANGELOG with bug fix entry

Medium Priority

  1. Document why different error handling approaches are used for SSR vs RSC
  2. Verify the instanceof Error check works with serialized errors
  3. Consider removing double negation in Jest config
  4. Add integration tests for streaming error propagation

Low Priority

  1. Add documentation explaining the new architecture
  2. Consider adding JSDoc comments to the new error handlers

🎉 Overall Assessment

Strong architectural improvements with proper separation of concerns. The core solution (removing react-dom/server from RSC bundle via conditional exports) is solid and well-implemented. The main issues are:

  1. Mandatory linting/formatting (per CLAUDE.md)
  2. Test coverage gaps
  3. Missing PR checklist items (docs, changelog)

Once these are addressed, this will be a valuable bug fix that improves error reporting in RSC contexts.

Recommendation: Request changes for the critical items (linting, typo, test coverage, checklist items) before merging.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cf9c7c and 37552c2.

📒 Files selected for processing (4)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/handleError.ts (1 hunks)
  • script/convert (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-on-rails-pro/jest.config.js
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/package.json
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/handleError.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • script/convert
🧬 Code graph analysis (1)
packages/react-on-rails-pro/src/handleError.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToString (4-4)
🪛 GitHub Actions: Lint JS and Ruby
script/convert

[error] 51-51: Style/TrailingCommaInArguments: Avoid comma after the last parameter of a method call. Offense detected at line 51:26 (autocorrectable). Rubocop exited with code 1.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22)
  • GitHub Check: claude-review
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
🔇 Additional comments (3)
packages/react-on-rails-pro/package.json (1)

10-11: LGTM! Clean test separation for RSC.

The test script changes correctly isolate RSC tests from the main test suite. Setting NODE_CONDITIONS=react-server in the dedicated test-rsc script ensures the RSC bundle is tested under the proper conditional export environment, which aligns with the PR objective of removing react-dom-server from the RSC bundle.

packages/react-on-rails-pro/src/handleError.ts (2)

1-5: LGTM! Imports are appropriate for SSR error handling.

The imports are clean and correctly scoped. Using renderToString from react-on-rails/ReactDOMServer is appropriate here since this is the pro package for SSR, not the RSC bundle mentioned in the PR objectives.


14-14: LGTM! Clean default export.

The default export is appropriate for this single-purpose module.

Comment on lines 7 to 8
const handleError = (options: ErrorOptions) => {
const msg = generateRenderingErrorMessage(options);
const reactElement = React.createElement('pre', null, msg);
const htmlString = renderToString(reactElement);
return Readable.from([htmlString]);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add explicit return type annotation.

The function should declare its return type explicitly for better type safety and developer experience.

Apply this diff:

-const handleError = (options: ErrorOptions) => {
+const handleError = (options: ErrorOptions): Readable => {
   const msg = generateRenderingErrorMessage(options);
   const reactElement = React.createElement('pre', null, msg);
   const htmlString = renderToString(reactElement);
   return Readable.from([htmlString]);
 };
🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/handleError.ts around lines 7 to 12, the
handleError function lacks an explicit return type; add an explicit return type
of Readable (from Node's stream module) to the function signature (e.g. annotate
the function as returning Readable) and ensure { Readable } is imported from
'stream' if not already, keeping the implementation unchanged.

import generateRenderingErrorMessage from 'react-on-rails/generateRenderingErrorMessage';

const handleError = (options: ErrorOptions) => {
const msg = generateRenderingErrorMessage(options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify error stack is included in development and test environments.

Based on learnings, error messages should include the stack trace in development and test environments to aid debugging. Please ensure generateRenderingErrorMessage includes the error stack (accessible via options.e.stack) when process.env.NODE_ENV is 'development' or 'test'.

Based on learnings.

Run the following script to examine the implementation:


🏁 Script executed:

#!/bin/bash
# Description: Verify generateRenderingErrorMessage includes error stack in dev/test

# Find and display the generateRenderingErrorMessage implementation
ast-grep --pattern 'function generateRenderingErrorMessage($$$) {
  $$$
}'

# Also check for arrow function syntax
ast-grep --pattern 'const generateRenderingErrorMessage = ($$$) => {
  $$$
}'

Length of output: 0


I need to search more broadly for the generateRenderingErrorMessage function. Let me locate it first and then examine its implementation.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find generateRenderingErrorMessage definition
rg -n "generateRenderingErrorMessage" --type ts --type js

# Also look at the handleError.ts file for context
echo "=== handleError.ts content ==="
fd -n "handleError.ts" --type f --exec cat {}

Length of output: 1224


Now let me examine the actual implementation of the function:


---

🏁 Script executed:

```shell
#!/bin/bash
# Display the generateRenderingErrorMessage implementation
cat packages/react-on-rails/src/generateRenderingErrorMessage.ts

Length of output: 1948


Include error stack in development and test environments.

The current implementation of generateRenderingErrorMessage (packages/react-on-rails/src/generateRenderingErrorMessage.ts, lines 35-66) does not check process.env.NODE_ENV. The error stack is only included in the returned message when serverSide is true, but it should also be included when process.env.NODE_ENV is 'development' or 'test' to aid debugging.

Modify the function to conditionally include e.stack in the message based on the environment, not just the serverSide flag.

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/handleError.ts around line 8 and in
packages/react-on-rails/src/generateRenderingErrorMessage.ts around lines 35-66,
the error message currently includes e.stack only when serverSide is true;
update generateRenderingErrorMessage so it also includes e.stack when
process.env.NODE_ENV === 'development' or 'test' (in addition to serverSide).
Change the conditional that builds the message to check (serverSide ||
process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') and
append e.stack when present, leaving other logic and return shape unchanged.

@claude
Copy link

claude bot commented Oct 29, 2025

Code Review for PR #1888: Don't include react-dom-server at the RSC bundle

Summary

This PR addresses issue #1886 by refactoring error handling and streaming utilities to prevent RSC bundles from importing react-dom/server. The changes introduce proper separation between standard SSR and RSC rendering paths.


Strengths

  1. Clear Separation of Concerns

    • New handleError.ts (standard SSR) vs handleErrorRSC.ts (RSC) provides clean separation
    • streamingUtils.ts consolidates shared streaming logic effectively
    • Conditional exports in package.json for react-server condition is the right approach
  2. Improved Error Handling

    • RSCRoute.tsx:84-86: Good addition to detect and throw serialized errors from RSC payloads
    • Consistent error streaming approach using pipeToTransform instead of writeChunk + endStream
  3. Test Coverage

    • New test ReactOnRailsRSC.test.tsx validates that RSC bundle doesn't import react-dom/server - excellent!
    • Separate test script test-rsc with proper Node conditions
  4. Type Safety

    • Making handleError a parameter to streamServerRenderedComponent provides better flexibility and type safety

⚠️ Issues & Concerns

1. Missing Test Coverage (Priority: High)

  • Issue: The PR checklist shows:
    • Add/update test to cover these changes
    • Update documentation
    • Update CHANGELOG file

All three items are unchecked. Per CLAUDE.md, these are required before merging.

  • Action Required:
    • Add tests for the new error handling paths in both handleError.ts and handleErrorRSC.ts
    • Test the error detection logic in RSCRoute.tsx:84-86
    • Update CHANGELOG with entry at the top of the file
    • Update relevant documentation about RSC bundle separation

2. Typo in Comment (Priority: Low)

// packages/react-on-rails-pro/src/RSCRoute.tsx:83
// RSC bundle will return an error object serilaized inside the RSC payload
//                                        ^^^^^^^^^^

Fix: Change "serilaized" to "serialized"

3. Potential Type Safety Issue (Priority: Medium)

// packages/react-on-rails-pro/src/streamingUtils.ts:245
const htmlResultStream = handleError({ e: error, name: componentName, serverSide: true });
pipeToTransform(htmlResultStream);
return readableStream as T;

The as T cast might mask type issues. Consider constraining the generic type or adding runtime validation.

4. Export Path Naming Inconsistency (Priority: Low)

// packages/react-on-rails/package.json:47
"./generateRenderingErrorMessage": "./lib/generateRenderingErrorMessage.js"

This export wasn't previously available. While needed for the refactor, ensure this is intentional API exposure.

5. Missing Error Handling Context (Priority: Medium)

In handleErrorRSC.ts:7, the error message is wrapped in new Error(msg) and passed to renderToPipeableStream. Consider if additional metadata (stack traces, component name) might be lost in this transformation compared to the standard SSR path.

6. Test Configuration Complexity (Priority: Low)

// jest.config.js:31-34
testEnvironmentOptions: process.env.NODE_CONDITIONS
  ? {
      customExportConditions: process.env.NODE_CONDITIONS.split(','),
    }
  : {},

This conditional config is necessary but adds complexity. Consider documenting this in a comment explaining why it's needed.


🔍 Code Quality Observations

  1. File Organization: The extraction of streaming utilities into streamingUtils.ts is excellent - reduces duplication and improves maintainability.

  2. Documentation: The extensive JSDoc comments in streamingUtils.ts (lines 49-64, 162-175) are very helpful.

  3. Backward Compatibility: The changes appear backward compatible - existing code paths should continue to work.


🛡️ Security & Performance

  • Security: No obvious security concerns. Error handling properly sanitizes and doesn't expose sensitive data.
  • Performance: The changes don't introduce performance regressions. Stream-based error handling is appropriate.

📋 Required Actions Before Merge

Per the repository's CLAUDE.md guidelines:

  1. Run linting: bundle exec rubocop (should be automatic via git hooks)
  2. ⚠️ Complete PR checklist:
    • Add/update tests for new error handling paths
    • Update documentation
    • Add CHANGELOG entry at the top of the file
  3. Run full test suite: rake to ensure all tests pass
  4. ⚠️ Fix the typo in RSCRoute.tsx:83

💡 Suggestions for Future Enhancement

  1. Consider adding integration tests that verify the complete error flow from component rendering through to final HTML output
  2. Document the react-server export condition pattern for other contributors
  3. Add error boundary tests that specifically exercise the new RSCRoute error detection

Recommendation

The core implementation is solid and addresses the stated issue effectively. However, this PR needs the following before merge approval:

  1. Complete the PR checklist items (tests, docs, changelog)
  2. Fix the typo
  3. Verify all CI checks pass

Great work on the architectural improvements! The separation of RSC and standard SSR error handling is the right approach.


Review Status: ⚠️ Changes Requested

Let me know if you have questions about any of these points or would like help with the test implementation!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37552c2 and d9d3b9c.

📒 Files selected for processing (1)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/jest.config.js
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/jest.config.js
🧬 Code graph analysis (1)
packages/react-on-rails-pro/jest.config.js (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles

Comment on lines +31 to +37
testEnvironmentOptions: process.env.NODE_CONDITIONS
? {
customExportConditions: process.env.NODE_CONDITIONS.split(','),
}
: {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Address the unresolved past review comment and handle edge cases.

The past review comment identified that the current default breaks RSC tests and causes pipeline failures. Additionally, the current implementation doesn't handle edge cases:

  • Empty string: "".split(',') returns [""] instead of an empty array
  • Whitespace: values are not trimmed after splitting

Apply this diff to address both the default and edge cases:

-  testEnvironmentOptions: process.env.NODE_CONDITIONS
-    ? {
-        customExportConditions: process.env.NODE_CONDITIONS.split(','),
-      }
-    : {},
+  testEnvironmentOptions: {
+    customExportConditions: process.env.NODE_CONDITIONS
+      ? process.env.NODE_CONDITIONS.split(',').map(s => s.trim()).filter(Boolean)
+      : ['react-server'],
+  },

This ensures:

  • RSC tests work correctly by defaulting to ['react-server']
  • Empty strings are filtered out
  • Whitespace is trimmed from condition names
🤖 Prompt for AI Agents
In packages/react-on-rails-pro/jest.config.js around lines 31 to 35, the current
testEnvironmentOptions logic breaks RSC tests and doesn't handle edge cases;
change it so when process.env.NODE_CONDITIONS is unset you default to
['react-server'], and when it is set split on ',' then map each entry to trimmed
string and filter out empty strings (so "" and whitespace-only entries are
removed) before assigning to customExportConditions. Ensure the final value is
an array of non-empty trimmed strings.

@claude
Copy link

claude bot commented Oct 29, 2025

Pull Request Review

Thank you for tackling issue #1886! This is a well-structured PR that successfully removes react-dom/server dependencies from the RSC bundle.

Strengths

Architecture & Design

  1. Excellent separation of concerns: Creating separate handleError.ts and handleErrorRSC.ts files clearly separates SSR and RSC error handling logic
  2. Smart refactoring: Extracting generateRenderingErrorMessage as a shared utility eliminates code duplication
  3. Conditional exports: Using conditional exports in package.json is the correct approach for RSC-specific bundles
  4. Proper stream handling: Consistently using pipeToTransform improves code consistency

Testing

  1. Good test coverage: The test mocking react-dom/server prevents regressions
  2. Environment-specific setup: Jest config with NODE_CONDITIONS properly simulates RSC environment

Error Handling

  1. Better error surfacing: Checking promiseResult instanceof Error ensures serialized errors are properly thrown

Issues & Concerns

Critical Issues

1. Incomplete error handling in handleErrorRSC.ts:

  • Serializing an Error through renderToPipeableStream is unusual
  • Have you tested this across SSR, hydration, and client navigation?
  • Consider wrapping in a React element like handleError.ts does

2. Type safety in streamingUtils.ts:

  • handleError.ts returns Readable, handleErrorRSC.ts returns PipeableStream
  • Does pipeToTransform handle both correctly? PipeableStream has .abort() but Readable doesn't

3. Error instanceof check (RSCRoute.tsx:84):

  • Errors serialized through RSC might not be true Error instances after deserialization
  • Add tests to verify this works correctly

Documentation

4. PR checklist incomplete:

  • Missing CHANGELOG update
  • Missing documentation updates
  • PR description still has template text

5. script/convert changes:

  • Test ignore patterns changed significantly - is this intentional?
  • Could hide test failures for older React/Node versions

Recommendations

High Priority

  1. Test error handling thoroughly in all scenarios
  2. Complete CHANGELOG and docs
  3. Update PR description
  4. Verify Error deserialization works

Medium Priority

  1. Add integration tests for RSC error handling
  2. Review script/convert changes
  3. Add comments explaining Error serialization

Security & Performance

  • Consider sanitizing error messages in production to avoid leaking sensitive info
  • Bundle size improvements are good
  • No performance concerns

Summary

Solid refactoring addressing the core issue. Main concerns:

  1. Error serialization pattern needs thorough testing
  2. Missing documentation/CHANGELOG
  3. Type safety with stream handling

Recommendation: Address critical issues and complete documentation before merging.

Great work on test coverage and architecture!

@claude
Copy link

claude bot commented Oct 29, 2025

Code Review: PR #1888 - Don't include react dom server at the rsc bundle

Summary

This PR addresses issue #1886 by removing dependencies on react-dom/server from the RSC bundle, improving error reporting for React Server Components. The changes refactor error handling and streaming utilities to properly support both regular SSR and RSC rendering paths.


✅ Strengths

  1. Good Separation of Concerns

    • Extracted generateRenderingErrorMessage into a shared utility (packages/react-on-rails/src/generateRenderingErrorMessage.ts), eliminating code duplication
    • Created separate error handlers for SSR (handleError.ts) and RSC (handleErrorRSC.ts)
    • Moved streaming utilities into a dedicated module (streamingUtils.ts)
  2. Excellent Test Coverage

    • Added ReactOnRailsRSC.test.tsx that explicitly verifies react-dom/server is not imported by mocking it to throw
    • Uses NODE_CONDITIONS=react-server to properly test RSC bundle behavior
    • Separate test scripts for regular tests and RSC-specific tests
  3. Proper Conditional Exports

    • Package.json exports use conditional "react-server" exports for ./@internal/base/full
    • This ensures RSC bundles get the correct variant without server DOM dependencies
  4. Error Propagation Improvements

    • RSCRoute.tsx now properly checks if promiseResult instanceof Error and re-throws (lines 84-86)
    • This ensures errors from RSC rendering are surfaced correctly instead of being rendered as values

⚠️ Issues & Concerns

1. Missing Test Validation (High Priority)

The PR checklist indicates:

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

All three items are unchecked. Please ensure:

  • Tests are added/updated (partially done, but need more coverage)
  • Documentation is updated to explain RSC error handling differences
  • CHANGELOG entry is added at the top of the file

2. Potential Type Safety Issue (full.rsc.ts:16-22)

handleError() {
  throw new Error('"handleError" function is not supported in RSC bundle');
},

serverRenderReactComponent() {
  throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
},

Concern: These functions throw runtime errors rather than providing compile-time safety. If code accidentally calls these in an RSC context, it will fail at runtime.

Suggestion: Consider using TypeScript's conditional types or branded types to make these errors compile-time instead of runtime errors, if possible.

3. Error Handling Return Type Inconsistency

  • handleError.ts (SSR): Returns Readable.from([htmlString]) - a Readable stream from an array
  • handleErrorRSC.ts: Returns renderToPipeableStream(new Error(msg), ...) - a PipeableStream

Concern: The function signature handleError: (options: ErrorOptions) => PipeableOrReadableStream suggests these should be compatible, but they're fundamentally different stream types. This works due to the union type, but it's not immediately obvious whether both paths are properly handled downstream.

Suggestion: Add integration tests that verify error handling works correctly for both SSR and RSC paths, ensuring streams are consumed properly.

4. Missing Error Context (handleErrorRSC.ts:7)

return renderToPipeableStream(new Error(msg), {
  filePathToModuleMetadata: {},
  moduleLoading: { prefix: '', crossOrigin: null },
});

Question: Why are filePathToModuleMetadata and moduleLoading always empty? Should these be configurable or derived from the rails context?

5. Incomplete Documentation in PR Description

The PR body still contains template placeholders:

"Remove this paragraph and provide a general description..."
"Remove this line after checking all the items here..."

Please update the PR description with:

  • Clear summary of what was changed and why
  • How the fix addresses the error reporting issue
  • Any breaking changes or migration notes

🔍 Code Quality Observations

Good Practices:

  • Proper use of TypeScript types throughout
  • Good code documentation (e.g., bufferStream function has excellent JSDoc)
  • Consistent error handling patterns
  • Proper stream cleanup with abort() calls

Minor Suggestions:

  1. streamingUtils.ts:86 - Consider extracting the event list to a constant:
const STREAM_EVENTS = ['data', 'error', 'end'] as const;
  1. Test organization - Consider adding more edge case tests:
    • What happens if error occurs mid-stream?
    • What happens if RSC component throws during rendering?
    • Error boundary behavior with RSC errors

🛡️ Security Considerations

No significant security concerns identified. Error messages are properly escaped through React element rendering.


⚡ Performance Considerations

Positive:

  • Stream buffering is efficient and prevents event loss
  • Proper resource cleanup with abort() calls
  • No unnecessary synchronous operations

Note: The buffering mechanism in bufferStream adds minimal memory overhead for event storage, which is acceptable for error scenarios.


📋 Required Actions Before Merge

CRITICAL (Must be done):

  1. ✅ Complete all checklist items in PR description
  2. ✅ Add CHANGELOG entry at the top of the file
  3. ✅ Update documentation about RSC error handling
  4. ✅ Remove template placeholders from PR description
  5. ✅ Verify bundle exec rubocop passes (per CLAUDE.md requirements)
  6. ✅ Ensure all files end with newline character

RECOMMENDED:

  1. Add integration tests for error scenarios in both SSR and RSC contexts
  2. Document why filePathToModuleMetadata is empty in handleErrorRSC.ts
  3. Consider compile-time safety for RSC bundle function restrictions

🎯 Overall Assessment

This is a well-structured refactoring that properly separates RSC and SSR concerns. The core implementation is solid, but the PR needs completion of documentation and changelog requirements per the project's contribution guidelines.

Recommendation: Request changes to complete the checklist items, then approve once addressed.


Review performed following CLAUDE.md guidelines and React on Rails project conventions.

@alexeyr-ci2 alexeyr-ci2 changed the title Don't include react dom server at the rsc bundle Don't include 'react-dom/server' in the RSC bundle Oct 29, 2025
@claude
Copy link

claude bot commented Oct 29, 2025

Pull Request Review: Don't include 'react-dom/server' in the RSC bundle

Overall Assessment

This is a well-architected PR that addresses a critical issue with React Server Components (RSC) bundles inadvertently including react-dom/server, which violates RSC's server/client boundary principles. The solution is clean, well-structured, and includes proper testing.

Recommendation: APPROVE with minor suggestions


Code Quality & Architecture ✅

Strengths:

  1. Excellent separation of concerns: The PR introduces handleError.ts and handleErrorRSC.ts as separate modules, each using the appropriate rendering API for their context:

    • handleError.ts uses renderToString from react-dom/server for traditional SSR
    • handleErrorRSC.ts uses renderToPipeableStream from react-on-rails-rsc/server.node for RSC
  2. Smart use of conditional exports: The package.json changes properly leverage the react-server condition to provide different implementations based on the runtime environment:

    "./@internal/base/full": {
      "react-server": "./lib/base/full.rsc.js",
      "default": "./lib/base/full.js"
    }
  3. Good code reuse: Extracting generateRenderingErrorMessage as a shared utility eliminates duplication while maintaining the specialized error handling for each context.

  4. Improved error handling flow: The streaming error handling is now more consistent:

    • Errors are properly piped through the transform stream
    • Console replay is maintained per-request
    • Error state is properly reset between chunks
  5. Well-organized refactoring: Moving shared streaming utilities to streamingUtils.ts improves code organization and maintainability.


Test Coverage ✅

Good:

  1. Critical test added: ReactOnRailsRSC.test.tsx ensures the RSC bundle doesn't import react-dom/server by mocking it to throw - this is exactly the right approach.

  2. Test infrastructure: The Jest configuration changes properly support the react-server condition via NODE_CONDITIONS environment variable.

  3. Separate test scripts: test and test-rsc scripts appropriately separate concerns.

Suggestions for Enhancement:

  1. Add integration test: Consider adding a test that verifies error messages are properly propagated through the streaming pipeline when errors occur during RSC payload generation.

  2. Test error serialization: The PromiseWrapper now checks if (promiseResult instanceof Error) - add a test case that verifies this error detection and re-throwing behavior.

  3. Test the new handleError implementations: Neither handleError.ts nor handleErrorRSC.ts have direct unit tests. Consider adding tests to verify:

    • Error message formatting
    • Stream/readable output structure
    • Edge cases (null errors, missing stack traces, etc.)

Potential Issues & Concerns

1. Error Object Serialization in RSC Payload (Medium Priority) ⚠️

Location: packages/react-on-rails-pro/src/RSCRoute.tsx:82-86

// In case that an error happened during the rendering of the RSC payload before the rendering of the component itself starts
// RSC bundle will return an error object serialized inside the RSC payload
if (promiseResult instanceof Error) {
  throw promiseResult;
}

Concern: The comment states "error object serialized inside the RSC payload", but instanceof Error checks the prototype chain. After deserialization, the object may not pass this check because:

  • Serialized errors may be plain objects
  • Cross-realm errors may have different prototypes
  • The RSC protocol may transform errors in unexpected ways

Suggestion: Add a more robust error detection:

const isError = (value: unknown): value is Error => {
  return value instanceof Error || 
         (typeof value === 'object' && value !== null && 'message' in value && 'stack' in value);
};

if (isError(promiseResult)) {
  throw promiseResult;
}

2. Empty Module Metadata in handleErrorRSC (Low Priority) 📝

Location: packages/react-on-rails-pro/src/handleErrorRSC.ts:7-9

return renderToPipeableStream(new Error(msg), {
  filePathToModuleMetadata: {},
  moduleLoading: { prefix: '', crossOrigin: null },
});

Question: Are empty values for filePathToModuleMetadata and moduleLoading appropriate? This might affect:

  • Source mapping in error stacks
  • Module resolution in RSC payloads
  • CORS headers for module loading

Suggestion: Consider whether these should be pulled from configuration or at least documented why empty values are safe here.

3. Trailing Newline Missing (CRITICAL for CI) ❗

Location: Multiple files

According to CLAUDE.md, ALL files must end with a newline character and RuboCop must pass with zero offenses. Please verify:

bundle exec rubocop

And ensure all modified files have trailing newlines.


Performance Considerations ✅

Positive impacts:

  1. Reduced bundle size: Removing react-dom/server from RSC bundles significantly reduces bundle size and prevents shipping server-only code to the client.

  2. Improved streaming: The refactored error handling properly pipes errors through the stream instead of writing chunks and ending the stream separately, which should improve consistency.

  3. Per-request isolation: The streaming trackers remain properly scoped per-request, preventing memory leaks.

No concerns with performance implications of these changes.


Security Concerns ✅

No major security issues identified.

Minor observations:

  1. Error message exposure: Errors are rendered with full stack traces in development. Ensure production environments have appropriate error filtering to avoid exposing sensitive information.

  2. Stream error handling: The buffering and error emission logic is sound and prevents unhandled rejections that could crash the Node process.


Best Practices Adherence

Following best practices:

  • ✅ Clear separation of server/client code
  • ✅ Proper TypeScript typing throughout
  • ✅ Good documentation in comments
  • ✅ Following existing code patterns
  • ✅ Changelog updated

Areas for improvement:

  • ⚠️ PR checklist not completed (test coverage, documentation sections unchecked)
  • ⚠️ Some test coverage gaps noted above

Specific File-by-File Notes

streamingUtils.ts

  • Well-documented, especially the bufferStream function documentation
  • Good handling of the buffered events pattern
  • The design principles comment is excellent

handleErrorRSC.ts

  • Clean and focused
  • Consider documenting why empty module metadata is safe

RSCRoute.tsx

ReactOnRailsRSC.test.tsx

  • Excellent approach using mock to ensure imports are prevented
  • Consider adding more comprehensive RSC error handling tests

full.rsc.ts

  • Good stub implementations that throw informative errors
  • Prevents accidental misuse of SSR functions in RSC context

Required Actions Before Merge

MANDATORY:

  1. ✅ Run bundle exec rubocop and fix ALL violations
  2. ✅ Ensure all files end with newline characters
  3. ⚠️ Complete PR checklist items or mark as N/A with strikethrough
  4. ⚠️ Address CHANGELOG entry if needed

RECOMMENDED:

  1. Consider the error serialization concern (TODO for first version #1) and add test coverage
  2. Add integration tests for error propagation through streaming
  3. Document the module metadata decision in handleErrorRSC

Summary

This is a solid PR that properly separates RSC and SSR error handling while removing problematic react-dom/server imports from RSC bundles. The architecture is sound, the code is clean, and it solves the reported issue effectively.

The test coverage ensures the critical constraint (no react-dom/server in RSC bundles) is enforced. With the minor improvements suggested above, this will be production-ready.

Great work! 🚀

@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/remove-react-dom-server-from-rsc-bundle branch from ed1e13d to ebf8032 Compare October 29, 2025 17:24
@claude
Copy link

claude bot commented Oct 29, 2025

Pull Request Review: Don't include 'react-dom/server' in the RSC bundle

Summary

This PR addresses issue #1886 by improving error handling for RSC (React Server Components) payload generation and removing react-dom/server imports from the RSC bundle. The changes enhance error visibility and ensure proper streaming of error messages to the Rails side.


Code Quality & Best Practices ✅

Strengths:

  1. Excellent separation of concerns: Creating separate handleError.ts and handleErrorRSC.ts files is the right approach for handling different rendering contexts
  2. Good refactoring: Extracting common streaming utilities to streamingUtils.ts improves code organization and reusability
  3. Proper abstraction: Making handleError a parameter to streamServerRenderedComponent is clean dependency injection
  4. Well-documented: The bufferStream function has excellent documentation explaining the problem it solves

Areas for Improvement:

  1. Test coverage concern: The new test ReactOnRailsRSC.test.tsx (lines 1-13) only verifies that the module doesn't import react-dom/server. Consider adding more comprehensive tests:

    • Test that errors during RSC payload generation are properly caught and streamed
    • Test the PromiseWrapper error handling logic (RSCRoute.tsx:84-85)
    • Test the new error streaming flow end-to-end
  2. CHANGELOG completeness: Per CLAUDE.md requirements, the PR checklist items should be completed:

    • ✅ CHANGELOG updated
    • ❌ Update documentation (no docs changes included)
    • ⚠️ Add/update tests (minimal test coverage added)

Potential Bugs & Issues ⚠️

1. Error Type Checking in RSCRoute (Critical)

Location: packages/react-on-rails-pro/src/RSCRoute.tsx:84

if (promiseResult instanceof Error) {
  throw promiseResult;
}

Issue: The instanceof Error check may not work reliably for errors serialized/deserialized through RSC payload. RSC uses a custom serialization format, and errors might be plain objects with error-like properties rather than true Error instances.

Recommendation: Consider a more robust check:

if (promiseResult && typeof promiseResult === 'object' && 
    ('message' in promiseResult || promiseResult instanceof Error)) {
  throw promiseResult instanceof Error ? promiseResult : new Error(String(promiseResult.message));
}

2. Missing Newline at End of File

Location: packages/react-on-rails-pro/src/handleErrorRSC.ts:14

Per CLAUDE.md critical requirements:

ALWAYS ensure files end with a newline character

The file appears to end without a trailing newline. Run rake autofix before committing.

3. Jest Configuration Complexity

Location: packages/react-on-rails-pro/jest.config.js:31-36

The conditional testEnvironmentOptions based on NODE_CONDITIONS environment variable is clever, but it adds complexity. Consider:

  • Adding a comment explaining why this is needed
  • Documenting in the test file itself that it requires special conditions

Performance Considerations 🚀

Positive Impact:

  1. Removing unnecessary imports: Excluding react-dom/server from RSC bundle will reduce bundle size
  2. Streaming improvements: The enhanced error handling maintains proper streaming behavior without blocking

Neutral:

  • The bufferStream wrapper adds minimal overhead for error safety
  • Error serialization through RSC payload is already established pattern

Security Concerns 🔒

Low Risk:

  1. Error message exposure: The changes expose error messages and stack traces to the client. This is consistent with existing behavior and appropriate for development, but ensure production environments have proper error filtering if needed.

  2. Error serialization: Using renderToPipeableStream(new Error(msg), ...) in handleErrorRSC.ts serializes error details. This is safe as msg comes from generateRenderingErrorMessage, which should already sanitize sensitive information.


Test Coverage 📊

Current State:

  • ✅ New test verifies RSC bundle doesn't import react-dom/server
  • ✅ Separate test script for RSC scenarios (test-rsc)
  • ❌ Missing integration tests for error handling flow
  • ❌ Missing tests for PromiseWrapper error detection

Recommendations:

  1. Add test for error during RSC payload generation being properly streamed
  2. Add test for PromiseWrapper throwing when receiving Error object
  3. Add test verifying error message format and content
  4. Consider adding a test that validates the entire error flow from server to client

Pre-Commit Checklist (CLAUDE.md Requirements) 📋

Before merging, ensure:

  • Run bundle exec rubocop and fix all violations
  • Run rake autofix to fix formatting issues (especially trailing newlines)
  • Add trailing newline to handleErrorRSC.ts
  • Complete PR checklist items (documentation and tests)
  • Consider adding more comprehensive test coverage

Overall Assessment ⭐

Rating: 7.5/10

This is a solid PR that addresses the core issue effectively. The architectural decisions (separate handleError implementations, refactored streaming utilities) are sound. The main concerns are:

  1. Incomplete adherence to CLAUDE.md requirements (trailing newlines, test coverage)
  2. Potential reliability issue with instanceof Error check in RSCRoute
  3. Missing comprehensive test coverage for the error handling improvements

Recommendation: Request changes to address the critical items (newline, instanceof check), then approve once those are resolved.


Additional Notes

The approach of using renderToPipeableStream for RSC errors instead of renderToString is correct - it maintains streaming consistency and properly serializes errors within the RSC payload format. Nice attention to detail! 👍

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)

78-83: Ensure shell errors update render state

When the shell fails, we stream the fallback HTML but leave renderState.hasErrors false and renderState.error unset, so downstream consumers never learn an error occurred. Please flag the error via reportError before piping the fallback output.

         onShellError(e) {
-          sendErrorHtml(convertToError(e));
+          const error = convertToError(e);
+          reportError(error);
+          sendErrorHtml(error);
         },
♻️ Duplicate comments (2)
packages/react-on-rails-pro/src/handleError.ts (2)

8-8: Error stack should include NODE_ENV check.

A past review comment noted that generateRenderingErrorMessage should include the error stack not only when serverSide is true, but also when process.env.NODE_ENV is 'development' or 'test' to aid debugging. This applies to the generateRenderingErrorMessage function in packages/react-on-rails/src/generateRenderingErrorMessage.ts.

Based on learnings and past review comments.


7-12: Add explicit return type annotation.

The handleError function should explicitly declare its return type as : Readable for better type safety and developer experience.

Apply this diff:

-const handleError = (options: ErrorOptions) => {
+const handleError = (options: ErrorOptions): Readable => {
   const msg = generateRenderingErrorMessage(options);
   const reactElement = React.createElement('pre', null, msg);
   const htmlString = renderToString(reactElement);
   return Readable.from([htmlString]);
 };
🧹 Nitpick comments (2)
packages/react-on-rails/src/base/full.ts (1)

19-22: LGTM! Type export enables RSC integration.

The export change makes this type available to RSC streaming modules as intended. The type definition itself is well-structured and safe.

Optional: Consider adding @public JSDoc tag for API clarity.

For consistency with BaseFullObjectType (line 27) and to clearly signal this is now part of the public API surface, consider adding a @public JSDoc tag to the comment above.

 /**
  * SSR-specific functions that extend the base client object to create a full object.
  * Typed explicitly to ensure type safety when mutating the base object.
+ * @public
  */
 export type ReactOnRailsFullSpecificFunctions = Pick<
packages/react-on-rails/src/generateRenderingErrorMessage.ts (1)

18-18: Consolidate console.error calls for better readability.

There are multiple separate console.error calls throughout this function (lines 18, 28, 38, 43, 47, 50, 51). Consider collecting all error information and logging it in a single console.error call to improve readability and performance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed1e13d and ebf8032.

📒 Files selected for processing (17)
  • CHANGELOG.md (1 hunks)
  • knip.ts (1 hunks)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (4 hunks)
  • packages/react-on-rails-pro/src/handleError.ts (1 hunks)
  • packages/react-on-rails-pro/src/handleErrorRSC.ts (1 hunks)
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (3 hunks)
  • packages/react-on-rails-pro/src/streamingUtils.ts (1 hunks)
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx (1 hunks)
  • packages/react-on-rails/package.json (1 hunks)
  • packages/react-on-rails/src/base/full.rsc.ts (1 hunks)
  • packages/react-on-rails/src/base/full.ts (1 hunks)
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts (1 hunks)
  • packages/react-on-rails/src/handleError.ts (1 hunks)
  • script/convert (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/react-on-rails/package.json
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails/src/base/full.rsc.ts
  • script/convert
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.test.tsx
  • packages/react-on-rails-pro/package.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/handleError.ts
  • CHANGELOG.md
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
🧠 Learnings (17)
📓 Common learnings
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • CHANGELOG.md
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • CHANGELOG.md
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • CHANGELOG.md
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • knip.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • CHANGELOG.md
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • knip.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • CHANGELOG.md
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
📚 Learning: 2025-06-11T12:34:58.182Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCProvider.tsx:0-0
Timestamp: 2025-06-11T12:34:58.182Z
Learning: `RSCProvider` intentionally keeps failed `getServerComponent` promises cached (even if rejected) to avoid repeated fetch attempts; callers must use `refetchComponent` to retry.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • packages/react-on-rails/src/handleError.ts
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-04-09T13:02:16.009Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.

Applied to files:

  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • knip.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
📚 Learning: 2025-09-29T12:23:13.615Z
Learnt from: CR
PR: shakacode/react_on_rails#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-29T12:23:13.615Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Applied to files:

  • knip.ts
📚 Learning: 2024-07-27T10:08:35.868Z
Learnt from: theforestvn88
PR: shakacode/react_on_rails#1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • knip.ts
🧬 Code graph analysis (6)
packages/react-on-rails-pro/src/handleError.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToString (4-4)
packages/react-on-rails/src/generateRenderingErrorMessage.ts (1)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/handleError.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToString (4-4)
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (4)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/base/client.ts (1)
  • handleError (262-267)
packages/react-on-rails-pro/src/streamingUtils.ts (1)
  • streamServerRenderedComponent (179-251)
packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (4)
packages/react-on-rails-pro/src/streamingUtils.ts (1)
  • streamServerRenderedComponent (179-251)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/base/client.ts (1)
  • handleError (262-267)
packages/react-on-rails-pro/src/streamingUtils.ts (4)
packages/react-on-rails/src/types/index.ts (5)
  • StreamRenderState (473-476)
  • RenderParams (220-224)
  • ErrorOptions (236-243)
  • assertRailsContextWithServerComponentMetadata (80-94)
  • RailsContextWithServerStreamingCapabilities (65-68)
packages/react-on-rails/src/serverRenderUtils.ts (3)
  • createResultObject (9-24)
  • validateComponent (30-36)
  • convertToError (26-28)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/isServerRenderResult.ts (1)
  • isServerRenderHash (8-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: markdown-link-check
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build
  • GitHub Check: claude-review
🔇 Additional comments (7)
CHANGELOG.md (1)

32-33: LGTM! Changelog entry accurately reflects the PR changes.

The entry correctly documents the improved RSC payload error handling feature, with proper formatting and attribution.

knip.ts (1)

45-45: LGTM! RSC base entry point correctly added.

The addition of 'src/base/full.rsc.ts!' properly registers the new RSC-specific base module as a production entry point, consistent with the PR's goal of improving RSC support.

packages/react-on-rails/src/handleError.ts (1)

6-10: LGTM! Clean refactoring improves maintainability.

The simplified implementation properly delegates error message generation to the centralized generateRenderingErrorMessage function, while maintaining the same external API. The use of renderToString to wrap the message in a <pre> element is appropriate for error display.

packages/react-on-rails-pro/src/RSCRoute.tsx (1)

80-88: Verify test coverage for Promise resolving to Error.

The new error handling logic checks if a Promise resolves to an Error instance (rather than rejecting) and throws it. A past reviewer noted this behavior isn't documented in React's use documentation and requested verification that tests cover this case.

Please confirm that test coverage exists for the scenario where React.use(promise) receives a Promise that resolves to an Error object (as opposed to a rejected Promise). This is critical for ensuring the RSC error serialization path works correctly.

You can verify by checking if tests exist that:

  1. Create an RSC payload that resolves to an Error
  2. Verify the Error is properly thrown and caught by RSCRouteErrorBoundary

Based on past review comments.

packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (3)

26-33: LGTM! Import updates align with refactored module structure.

The updated import paths for handleError and streaming utilities correctly reference the new dedicated modules, improving code organization.


54-90: LGTM! Error handling properly integrated with streaming pipeline.

The refactored error handling flow correctly:

  1. Removes direct stream writes (writeChunk, endStream)
  2. Routes errors through pipeToTransform for consistent stream handling
  3. Uses the centralized handleError function for error HTML generation

This improves maintainability and ensures consistent error handling across the RSC rendering pipeline.


101-101: LGTM! handleError properly injected into streaming component.

The addition of handleError as the third argument to streamServerRenderedComponent correctly integrates the error handling callback into the streaming flow.

return msg;
}

return 'undefined';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return undefined instead of the string 'undefined'.

Line 63 returns the string literal 'undefined', which is incorrect. For client-side rendering, this should return undefined (the value, not a string).

Apply this diff:

-  return 'undefined';
+  return undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return 'undefined';
return undefined;
🤖 Prompt for AI Agents
In packages/react-on-rails/src/generateRenderingErrorMessage.ts around line 63,
the function currently returns the string literal 'undefined' but should return
the JavaScript undefined value; replace the string return with a bare undefined
return (i.e., return undefined) so client-side rendering receives the correct
undefined value.

@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/remove-react-dom-server-from-rsc-bundle branch from ebf8032 to 7b00477 Compare October 29, 2025 17:43
@AbanoubGhadban AbanoubGhadban changed the base branch from master to prepare-for-removing-react-server-dom-from-rsc-bundle October 29, 2025 17:44
Base automatically changed from prepare-for-removing-react-server-dom-from-rsc-bundle to master October 30, 2025 06:54
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/remove-react-dom-server-from-rsc-bundle branch from dc06b64 to cf466b7 Compare October 30, 2025 06:55
@AbanoubGhadban AbanoubGhadban merged commit 43accf3 into master Oct 30, 2025
19 of 20 checks passed
@AbanoubGhadban AbanoubGhadban deleted the abanoubghadban/remove-react-dom-server-from-rsc-bundle branch October 30, 2025 06:58
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
packages/react-on-rails-pro/src/handleError.ts (1)

5-8: Add explicit return type annotation.

The function should declare its return type explicitly for better type safety and developer experience.

Apply this diff:

-const handleError = (options: ErrorOptions) => {
+const handleError = (options: ErrorOptions): Readable => {
   const htmlString = handleErrorAsString(options);
   return Readable.from([htmlString]);
 };
packages/react-on-rails/src/generateRenderingErrorMessage.ts (1)

63-63: Return undefined instead of the string 'undefined'.

Line 63 returns the string literal 'undefined', which is incorrect. For client-side rendering, this should return undefined (the value, not a string).

Apply this diff:

-  return 'undefined';
+  return undefined;
packages/react-on-rails/src/base/full.rsc.ts (1)

15-23: Fix type signature mismatch in stub functions.

The stub implementations don't match the expected signatures from ReactOnRailsFullSpecificFunctions. While the functions throw errors immediately, they must still satisfy the type contract to maintain type safety.

Apply this diff to correct the function signatures:

   const reactOnRailsFullSpecificFunctions: ReactOnRailsFullSpecificFunctions = {
-    handleError() {
+    handleError(options) {
+      void options; // Mark as used
       throw new Error('"handleError" function is not supported in RSC bundle');
     },
 
-    serverRenderReactComponent() {
+    serverRenderReactComponent(options) {
+      void options; // Mark as used
       throw new Error('"serverRenderReactComponent" function is not supported in RSC bundle');
     },
   };
🧹 Nitpick comments (2)
packages/react-on-rails-pro/src/handleError.ts (1)

5-8: Consider consolidating with core handleError if duplication becomes problematic.

Both packages/react-on-rails/src/handleError.ts and this file follow similar patterns (call handleErrorAsString/generateRenderingErrorMessage, wrap result). While they serve different return types (string vs stream), if the duplication becomes difficult to maintain, consider extracting a shared helper or creating a factory pattern.

packages/react-on-rails-pro/src/handleErrorRSC.ts (1)

5-11: Add explicit return type annotation.

For consistency with other handlers and better type safety, add an explicit return type to the handleError function.

Apply this diff:

+import type { PipeableStream } from 'react-on-rails-rsc/server.node';
+
-const handleError = (options: ErrorOptions) => {
+const handleError = (options: ErrorOptions): PipeableStream => {
   const msg = generateRenderingErrorMessage(options);
   return renderToPipeableStream(new Error(msg), {
     filePathToModuleMetadata: {},
     moduleLoading: { prefix: '', crossOrigin: null },
   });
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebf8032 and cf466b7.

📒 Files selected for processing (18)
  • CHANGELOG.md (1 hunks)
  • knip.ts (1 hunks)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (4 hunks)
  • packages/react-on-rails-pro/src/handleError.ts (1 hunks)
  • packages/react-on-rails-pro/src/handleErrorRSC.ts (1 hunks)
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (3 hunks)
  • packages/react-on-rails-pro/src/streamingUtils.ts (4 hunks)
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx (1 hunks)
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx (1 hunks)
  • packages/react-on-rails/package.json (1 hunks)
  • packages/react-on-rails/src/base/full.rsc.ts (1 hunks)
  • packages/react-on-rails/src/base/full.ts (2 hunks)
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts (2 hunks)
  • packages/react-on-rails/src/handleError.ts (1 hunks)
  • script/convert (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/package.json
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/package.json
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • knip.ts
  • script/convert
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
🧠 Learnings (15)
📓 Common learnings
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
PR: shakacode/react_on_rails#1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • packages/react-on-rails/src/handleError.ts
  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • packages/react-on-rails-pro/src/handleErrorRSC.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/base/full.rsc.ts
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
PR: shakacode/react_on_rails#1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
PR: shakacode/react_on_rails#1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • packages/react-on-rails-pro/src/handleError.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
PR: shakacode/react_on_rails#1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
📚 Learning: 2024-07-27T10:08:35.868Z
Learnt from: theforestvn88
PR: shakacode/react_on_rails#1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
📚 Learning: 2025-04-09T13:02:16.009Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.

Applied to files:

  • packages/react-on-rails-pro/src/streamingUtils.ts
  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails/src/generateRenderingErrorMessage.ts
  • packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx
  • packages/react-on-rails/src/base/full.rsc.ts
🧬 Code graph analysis (7)
packages/react-on-rails/src/handleError.ts (3)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToString (4-4)
packages/react-on-rails-pro/src/handleErrorRSC.ts (4)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/ReactDOMServer.cts (1)
  • renderToPipeableStream (4-4)
packages/react-on-rails-pro/src/handleError.ts (2)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails-pro/src/streamingUtils.ts (3)
packages/react-on-rails/src/base/full.ts (1)
  • handleError (44-46)
packages/react-on-rails/src/base/full.rsc.ts (1)
  • handleError (16-18)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)
packages/react-on-rails-pro/src/streamingUtils.ts (1)
  • streamServerRenderedComponent (179-251)
packages/react-on-rails/src/generateRenderingErrorMessage.ts (1)
packages/react-on-rails/src/types/index.ts (1)
  • ErrorOptions (236-243)
packages/react-on-rails/src/base/full.rsc.ts (2)
packages/react-on-rails/src/base/full.ts (3)
  • createBaseFullObject (34-66)
  • BaseFullObjectType (29-32)
  • ReactOnRailsFullSpecificFunctions (19-22)
packages/react-on-rails/src/base/client.ts (2)
  • createBaseClientObject (58-275)
  • BaseClientObjectType (43-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: markdown-link-check
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: claude-review
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: build
  • GitHub Check: lint-js-and-ruby
🔇 Additional comments (10)
packages/react-on-rails/src/handleError.ts (1)

1-12: LGTM!

Clean separation of concerns—delegates message generation to generateRenderingErrorMessage and wraps the result in HTML. The refactoring successfully isolates the React/renderToString dependency from the core error message generation logic.

packages/react-on-rails-pro/tests/ReactOnRailsRSC.rsc.test.tsx (1)

1-13: Excellent test design for validating the PR objective.

The test directly validates that ReactOnRailsRSC doesn't import react-dom/server by mocking it to throw. This ensures the RSC bundle remains free of server rendering dependencies.

packages/react-on-rails-pro/src/handleErrorRSC.ts (1)

7-10: Verify empty manifest is appropriate for error rendering.

The error handler uses an empty filePathToModuleMetadata and default moduleLoading config. Ensure this is the intended behavior for error scenarios, or if the manifest should be obtained from the rendering context.

packages/react-on-rails-pro/tests/RSCSerialization.rsc.test.tsx (1)

17-44: LGTM! Comprehensive RSC serialization tests.

The tests validate both success and error paths for RSC streaming, ensuring proper round-trip encoding/decoding of plain objects and Error instances. Well-structured with clear test cases.

packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (3)

26-31: LGTM! Proper modularization of error handling.

The updated imports correctly reference the local handleError implementation and the refactored streamServerRenderedComponent utility, improving code organization.


58-59: Improved error stream handling.

Using pipeToTransform instead of manual writeChunk/endStream calls provides better stream composition and aligns with the updated streaming architecture throughout the codebase.


104-104: LGTM! Updated to three-argument signature.

Correctly passes handleError as the third argument to streamServerRenderedComponent, enabling proper error handling in the streaming pipeline.

packages/react-on-rails-pro/src/streamingUtils.ts (3)

29-29: LGTM! Clean type additions for improved API design.

The ErrorOptions import and new exported types (StreamingTrackers, StreamRenderer) enhance type safety and provide clear contracts for the streaming pipeline.

Also applies to: 152-161


182-182: LGTM! Dependency injection improves flexibility.

Passing handleError as a parameter enables different error handling strategies per rendering mode and improves testability.


237-249: LGTM! Excellent refactoring to pipe-based error streaming.

The shift from writeChunk/endStream to pipeToTransform creates a more consistent streaming architecture. The error handling flow correctly:

  • Emits JS errors when throwJsErrors is enabled
  • Converts exceptions to standard Error objects
  • Pipes error HTML streams through the transform pipeline

The handleError parameter contract assumes implementations won't throw synchronously; any such error would propagate uncaught from this catch block. Given this is a dependency injection pattern with compile-time type enforcement, this is acceptable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants