Skip to content

Add TanStack Router SSR integration utility#2516

Merged
justin808 merged 19 commits intomasterfrom
jg/tanstack-query-support
Mar 9, 2026
Merged

Add TanStack Router SSR integration utility#2516
justin808 merged 19 commits intomasterfrom
jg/tanstack-query-support

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Mar 4, 2026

Summary

Adds first-class TanStack Router SSR support for React on Rails Pro through react-on-rails-pro/tanstack-router.

What Changed

  • Added createTanStackRouterRenderFunction() and serverRenderTanStackAppAsync() to react-on-rails-pro/tanstack-router.
  • The server-side path uses the Pro Node Renderer async SSR flow (rendering_returns_promises = true) and TanStack Router's public router.load() API.
  • The client-side path hydrates from server-provided router state, supports RouterClient and TanStack Router's $_TSR SSR payload, preserves client-only initial-load behavior, and keeps the internal hydration payload out of AppWrapper props.
  • Extended the shared React on Rails render-function pipeline so server render results can return renderedHtml as a React element plus clientProps, including correct async server-render-hash handling.
  • Updated the Rails react_component helper to merge server-rendered clientProps into the client hydration payload.
  • Added docs, changelog, and dummy-app coverage for the new integration.

Scope And Compatibility

  • TanStack Router SSR helper support is Pro-only.
  • Requires the React on Rails Pro Node Renderer with rendering_returns_promises = true.
  • Supported @tanstack/react-router versions: >=1.139.0 <2.0.0.
  • Client-only TanStack Router usage in OSS remains unchanged.

Coverage

  • Added Jest coverage for the Pro TanStack Router helper and the shared server-render pipeline changes.
  • Added Rails helper coverage for clientProps merging.
  • Added a Pro dummy app example and system coverage for async TanStack Router SSR and navigation.

Closes #2298

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds TanStack Router integration and wiring: package export and optional peer dependency; TypeScript types; server sync/async render helpers; client hydration utility; threading of server-returned clientProps into Rails helper merge; docs, tests, demo apps, and example routes.

Changes

Cohort / File(s) Summary
Package manifest
packages/react-on-rails/package.json
Add public export ./tanstack-router and optional peerDependency @tanstack/react-router.
TanStack Router entry & types
packages/react-on-rails/src/tanstack-router/index.ts, packages/react-on-rails/src/tanstack-router/types.ts
New public entry exposing createTanStackRouterRenderFunction, exported types, and a lightweight router abstraction.
Server rendering
packages/react-on-rails/src/tanstack-router/serverRender.ts
Add serverRenderTanStackApp and serverRenderTanStackAppAsync with sync injection, internals validation, SSR flagging, and dehydrated-state construction.
Client hydration
packages/react-on-rails/src/tanstack-router/clientHydrate.ts
Add clientHydrateTanStackApp to create browser-history router, apply dehydrated state or inject matches, preserve router ref, and run post-hydration load.
TanStack utils
packages/react-on-rails/src/tanstack-router/utils.ts
Add search normalization helpers normalizeSearch and locationSearch.
Type & render plumbing
packages/react-on-rails/src/types/index.ts, packages/react-on-rails/src/serverRenderReactComponent.ts, packages/react-on-rails/src/serverRenderUtils.ts, packages/react-on-rails/src/isServerRenderResult.ts
Introduce optional clientProps into render types and propagate through server-render processing; update server hash detection and async render handling.
Render-function wrapper
packages/react-on-rails/src/tanstack-router/index.ts
Create render-function factory that delegates to server/client helpers and returns a ReactOnRails-compatible renderFunction.
Docs & guides
docs/*, docs/building-features/tanstack-router.md, docs/core-concepts/render-functions.md, docs/api-reference/*
Add TanStack Router guide, update render-function docs and API docs to document clientProps/dehydration and new return shapes.
Rails helper & merge logic
react_on_rails/lib/react_on_rails/helper.rb
Add merge_server_rendered_client_props! and call site to validate/merge result["clientProps"] into render_options.props before generating client script; JSON/Hash validation and errors.
Ruby tests for helper
react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
Add tests covering merging clientProps into props, JSON-string props handling, and error cases.
Demo apps, routes & views
react_on_rails/spec/dummy/..., react_on_rails_pro/spec/dummy/..., react_on_rails/spec/dummy/package.json, react_on_rails_pro/spec/dummy/package.json
Add example TanStack Router components, controllers, views, routes, system tests, and add @tanstack/react-router to demo package manifests.
Client/demo components
react_on_rails/spec/dummy/client/app/startup/TanStackRouterApp.jsx, react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TanStackRouterAppAsync.jsx
Add synchronous and async demo entries; async variant returns Promise-based SSR result and marks as renderFunction.
Tests
packages/react-on-rails/tests/tanstackRouter.test.ts, packages/react-on-rails/tests/serverRenderReactComponent.test.ts
Add comprehensive tests for TanStack Router renderFunction (server & client), hydration with dehydrated payloads, and clientProps propagation.
Server render result detection
packages/react-on-rails/src/isServerRenderResult.ts
Make isServerRenderHash accept unknown and use runtime property checks for robust detection.

Sequence Diagram(s)

sequenceDiagram
    participant Controller as Rails Controller
    participant Server as SSR Process
    participant Router as TanStack Router
    participant Store as Router.__store
    participant Renderer as React Renderer

    Controller->>Server: call renderFunction(props with url, railsContext.serverSide=true)
    Server->>Router: createRouter(createMemoryHistory(initialEntries=[url]))
    Server->>Router: validate internals / matchRoutes(path)
    Server->>Store: inject matches sync (__store setState)
    Server->>Router: router.ssr = true
    Server->>Router: dehydrated = router.dehydrate()
    Server->>Renderer: build appElement (RouterProvider + AppWrapper)
    Renderer-->>Server: appElement
    Server-->>Controller: return { renderedHtml: appElement, clientProps: { __tanstackRouterDehydratedState } }
Loading
sequenceDiagram
    participant Browser as Client Browser
    participant Hydrator as clientHydrateTanStackApp
    participant History as createBrowserHistory
    participant Router as TanStack Router
    participant Effect as post-hydration effect

    Browser->>Hydrator: init(props may include dehydratedState)
    Hydrator->>History: createBrowserHistory()
    Hydrator->>Router: createRouter(browserHistory)
    alt dehydratedState present
        Hydrator->>Router: router.hydrate(dehydratedState)
        Hydrator->>Router: router.ssr = true
    else no dehydratedRouter
        Hydrator->>Router: router.matchRoutes(path) -> inject matches into __store
    end
    Browser->>Effect: useEffect -> if dehydratedState then router.load()
    Router-->>Browser: RouterProvider renders hydrated app
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through code with tiny paws and cheer,

Wound routes on server, left breadcrumbs for here,
Rails and TanStack clasped hands for a while,
Hydration snugly finished with a twitch and a smile —🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main change: adding a TanStack Router SSR integration utility (createTanStackRouterRenderFunction) to the react-on-rails package.
Linked Issues check ✅ Passed The PR fulfills the primary coding objectives from issue #2298: it provides documented, working code that integrates TanStack Router with React on Rails SSR through the new createTanStackRouterRenderFunction utility, including both server-side and client-side rendering paths with router hydration support.
Out of Scope Changes check ✅ Passed All changes align with the PR objectives. The addition of TanStack Router support extends the framework, and supporting type and infrastructure changes (types/index.ts, serverRenderReactComponent.ts, helper.rb) are necessary to enable clientProps hydration and server render result handling.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jg/tanstack-query-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON, but it could not run because Privacy Mode (Legacy) is turned on. To enable Bugbot Autofix, switch your privacy mode in the Cursor dashboard.

Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5226885a84

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/index.ts Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 4, 2026

Greptile Summary

This PR adds a new react-on-rails/tanstack-router sub-entry to the react-on-rails npm package, providing createTanStackRouterRenderFunction() — a factory that produces a React on Rails render function handling both synchronous SSR and client-side hydration for TanStack Router apps.

The implementation is well-structured with clear comments explaining private-API workarounds. However, three issues need addressing:

  • Missing serverRenderTanStackAppAsync export (index.ts:39): The async render path described in the PR for Pro NodeRenderer (rendering_returns_promises) is implemented in serverRender.ts but never re-exported from index.ts, making it completely inaccessible via the public API react-on-rails/tanstack-router.

  • router.load() is unreachable dead code (clientHydrate.ts:70–77): The guard if (router.state.status !== 'idle') can never be true when the sync injection path succeeds, because that path explicitly sets status: 'idle'. This means router.load() is never called post-hydration, which could silently skip deferred loaders and navigation hooks.

  • Internal __tanstackRouterDehydratedState prop leaks into AppWrapper (serverRender.ts:60–64, clientHydrate.ts:82): The dehydrated state prop is spread into the user's AppWrapper component on both server and client, which is an internal implementation detail that should be filtered out before passing to user code.

Confidence Score: 2/5

  • Not safe to merge as-is — the async render path is completely inaccessible via the public API, and client-side hydration is missing a critical router.load() call post-hydration.
  • Two concrete logic-level issues need resolution before merging: (1) the missing serverRenderTanStackAppAsync export makes an advertised feature inaccessible to Pro NodeRenderer users, and (2) the always-false router.state.status !== 'idle' guard prevents router.load() from ever executing post-hydration, which could silently break deferred data loading and navigation hooks. A third style issue (internal prop leaking to AppWrapper) should also be addressed to prevent runtime warnings. The code is otherwise well-structured, but these three issues compound to make the PR unsafe to merge.
  • packages/react-on-rails/src/tanstack-router/index.ts (missing export) and packages/react-on-rails/src/tanstack-router/clientHydrate.ts (dead-code guard) need the most critical attention; packages/react-on-rails/src/tanstack-router/serverRender.ts should also be updated to filter internal props before passing to AppWrapper.

Sequence Diagram

sequenceDiagram
    participant RoR as React on Rails
    participant RenderFn as createTanStackRouterRenderFunction
    participant Server as serverRenderTanStackApp
    participant Client as clientHydrateTanStackApp
    participant Router as TanStack Router

    Note over RoR,Router: Server-Side Rendering (sync)
    RoR->>RenderFn: renderFn(props, railsContext{serverSide:true})
    RenderFn->>Server: serverRenderTanStackApp(options, props, railsContext)
    Server->>Router: options.createRouter()
    Server->>Router: router.update({history: memoryHistory})
    Server->>Router: validateRouterInternals(router)
    Server->>Router: router.matchRoutes(pathname, search)
    Server->>Router: router.__store.setState({status:'idle', matches})
    Server->>Router: router.ssr = true
    Server->>Router: router.dehydrate()
    Server-->>RenderFn: ReactElement (with __tanstackRouterDehydratedState in props)
    RenderFn-->>RoR: ReactElement → renderToString()

    Note over RoR,Router: Client-Side Hydration
    RoR->>RenderFn: renderFn(props, railsContext{serverSide:false})
    RenderFn->>Client: clientHydrateTanStackApp(options, props, railsContext)
    Client->>Router: options.createRouter()
    Client->>Router: router.update({history: browserHistory})
    Client->>Router: router.__store.setState({status:'idle', matches})
    Client->>Router: router.ssr = true
    Client->>Router: router.hydrate(dehydratedState.dehydratedRouter)
    Client-->>RenderFn: ReactElement (AppComponent)
    RenderFn-->>RoR: ReactElement → hydrateRoot()
    Note over Client,Router: useEffect (post-mount): router.load() [currently unreachable]
Loading

Last reviewed commit: 5226885

Comment thread packages/react-on-rails-pro/src/tanstack-router/index.ts
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
packages/react-on-rails/src/tanstack-router/types.ts (1)

11-21: Widen search value typing to avoid rejecting valid typed routers.

Lines [13] and [20] use Record<string, string>, which is stricter than many TanStack Router search schemas (often non-string values). This can make createRouter return types harder to assign.

🔧 Proposed change
   matchRoutes: (
     pathname: string,
-    locationSearch: Record<string, string>,
+    locationSearch: Record<string, unknown>,
     opts?: { throwOnError?: boolean },
   ) => unknown[];
@@
     location: {
       pathname: string;
-      search: Record<string, string>;
+      search: Record<string, unknown>;
       searchStr: string;
       hash: string;
       href: string;
     };

Also applies to: 25-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-on-rails/src/tanstack-router/types.ts` around lines 11 - 21,
The type for search is too narrow (Record<string, string>) and rejects valid
TanStack Router schemas; update the search typing used in matchRoutes signature
and in state.location (and the other occurrences around the same area) to a
wider type such as Record<string, unknown> (or Record<string, any>) so
non-string search values are accepted; locate the matchRoutes declaration and
the state { location { search } } type blocks and replace the Record<string,
string> occurrences with the wider type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-on-rails/package.json`:
- Around line 62-67: The peer dependency range for "@tanstack/react-router" is
too loose (">= 1.0.0") and can pull in breaking v2+ changes; update the
peerDependencies entry for "@tanstack/react-router" to constrain to v1.x (for
example ">=1.0.0 <2.0.0") so consumers cannot install incompatible v2+ releases
that break uses of internal APIs like router.__store.setState and router.ssr.

In `@packages/react-on-rails/src/tanstack-router/clientHydrate.ts`:
- Around line 44-47: The initialization forcibly setting status to 'idle'
prevents the later condition that calls router.load() from ever running; remove
the status override so the initial state uses the router's real status and then
unconditionally call router.load() on first client render (respecting router.ssr
= true behavior) to enable client-side navigation — update the clientHydrate
initialization that assigns status and resolvedLocation and ensure router.load()
is invoked (call router.load() directly or remove the status check gating it) so
that Router.load() executes after the first render.

In `@packages/react-on-rails/src/tanstack-router/index.ts`:
- Line 39: The public entrypoint currently only re-exports types
(TanStackRouterOptions, DehydratedRouterState) and the sync factory but omits
the async SSR helper; add an explicit export for serverRenderTanStackAppAsync so
consumers can import it from the package surface (e.g. export {
serverRenderTanStackAppAsync } from './serverRenderTanStackAppAsync' or from the
module where it's implemented) and similarly ensure the same export is added in
the other export block referenced (lines ~106-117) so the async helper is
available from both public entrypoints; reference the symbol
serverRenderTanStackAppAsync when adding the export.

---

Nitpick comments:
In `@packages/react-on-rails/src/tanstack-router/types.ts`:
- Around line 11-21: The type for search is too narrow (Record<string, string>)
and rejects valid TanStack Router schemas; update the search typing used in
matchRoutes signature and in state.location (and the other occurrences around
the same area) to a wider type such as Record<string, unknown> (or
Record<string, any>) so non-string search values are accepted; locate the
matchRoutes declaration and the state { location { search } } type blocks and
replace the Record<string, string> occurrences with the wider type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60276abb-c2aa-4f10-860f-3231180d4b00

📥 Commits

Reviewing files that changed from the base of the PR and between 2e5dbbc and 5226885.

📒 Files selected for processing (5)
  • packages/react-on-rails/package.json
  • packages/react-on-rails/src/tanstack-router/clientHydrate.ts
  • packages/react-on-rails/src/tanstack-router/index.ts
  • packages/react-on-rails/src/tanstack-router/serverRender.ts
  • packages/react-on-rails/src/tanstack-router/types.ts

Comment thread packages/react-on-rails/package.json Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails-pro/src/tanstack-router/index.ts
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/types.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 4, 2026

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.6 KB (+0.03% 🔺)
react-on-rails/client bundled (gzip) (time) 62.6 KB (+0.03% 🔺)
react-on-rails/client bundled (brotli) 53.65 KB (-0.1% 🔽)
react-on-rails/client bundled (brotli) (time) 53.65 KB (-0.1% 🔽)
react-on-rails-pro/client bundled (gzip) 63.6 KB (+0.02% 🔺)
react-on-rails-pro/client bundled (gzip) (time) 63.6 KB (+0.02% 🔺)
react-on-rails-pro/client bundled (brotli) 54.62 KB (-0.1% 🔽)
react-on-rails-pro/client bundled (brotli) (time) 54.62 KB (-0.1% 🔽)
registerServerComponent/client bundled (gzip) 127.37 KB (+0.01% 🔺)
registerServerComponent/client bundled (gzip) (time) 127.37 KB (+0.01% 🔺)
registerServerComponent/client bundled (brotli) 61.61 KB (+0.1% 🔺)
registerServerComponent/client bundled (brotli) (time) 61.61 KB (+0.1% 🔺)
wrapServerComponentRenderer/client bundled (gzip) 121.94 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 121.94 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 56.65 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 56.65 KB (0%)

Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Comment thread packages/react-on-rails-pro/src/tanstack-router/index.ts
Comment thread packages/react-on-rails/src/tanstack-router/types.ts Outdated
Comment thread packages/react-on-rails/package.json Outdated
@justin808 justin808 added the P2 Backlog priority label Mar 5, 2026
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/clientHydrate.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Comment thread packages/react-on-rails/src/tanstack-router/index.ts Outdated
Comment thread packages/react-on-rails/package.json Outdated
Copy link
Copy Markdown
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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/api-reference/view-helpers-api.md`:
- Line 27: Update the wording for the renderer-function sentence in the
component_name description: change "dom (client side only)" to "DOM (client-side
only)" and hyphenate "renderer function" consistently where applicable; ensure
the phrase still clarifies that a "renderer function" takes a third parameter of
a DOM ID and that it's client-side only, referencing the symbol component_name
and the term "renderer function"/"Render-Function".

In `@docs/building-features/tanstack-router.md`:
- Around line 13-15: Update the installation code block in the tanstack-router
docs to be package-manager-agnostic by adding npm and yarn variants alongside
the existing pnpm command; specifically replace the single-line pnpm install
snippet with three lines showing "npm install `@tanstack/react-router`", "yarn add
`@tanstack/react-router`", and "pnpm add `@tanstack/react-router`" in the same
fenced bash block so readers can use their preferred package manager.

In `@packages/react-on-rails/src/tanstack-router/serverRender.ts`:
- Around line 11-27: validateRouterInternals currently only checks
__store.setState and matchRoutes but not the shape of router.state.location,
which later leads to unsafe dereferences of router.state.location.pathname and
.search; update validateRouterInternals to also verify router.state and
router.state.location exist and that location.pathname and location.search are
strings (or at least defined), and throw a clear compatibility Error (same style
as existing messages) mentioning validateRouterInternals and
router.state.location so callers get a helpful message if `@tanstack/react-router`
changes its location shape.

In `@react_on_rails/lib/react_on_rails/helper.rb`:
- Around line 583-588: Reorder and tighten the checks around client_props: first
handle nil (return if client_props.nil?), then validate its type (unless
client_props.is_a?(Hash) raise ReactOnRails::Error ...), and only after
confirming it's a Hash call client_props.empty? and return if empty; this
ensures client_props.empty? is never invoked on non-Hash scalars—refer to the
local variable client_props and the raised ReactOnRails::Error for where to
apply the change.
- Around line 590-596: The code in the react_on_rails helper incorrectly raises
ReactOnRails::Error when render_options.props is a JSON string but clientProps
exists; instead, update the merge logic in the block referencing existing_props
and render_options.props to accept a JSON-string props by parsing the JSON
string into a Hash (or safely converting it) before merging clientProps,
handling JSON parse errors by raising the current error only if parsing fails;
ensure the same merge path is used whether props started as a Hash or a JSON
string so react_component’s documented JSON-string support remains compatible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66ced15e-de29-4f26-af36-0cfa3c4b4d01

📥 Commits

Reviewing files that changed from the base of the PR and between 5226885 and 2c78727.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • docs/README.md
  • docs/api-reference/javascript-api.md
  • docs/api-reference/view-helpers-api.md
  • docs/building-features/react-router.md
  • docs/building-features/tanstack-router.md
  • docs/core-concepts/render-functions.md
  • docs/getting-started/using-react-on-rails.md
  • docs/introduction.md
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • packages/react-on-rails/src/serverRenderUtils.ts
  • packages/react-on-rails/src/tanstack-router/clientHydrate.ts
  • packages/react-on-rails/src/tanstack-router/index.ts
  • packages/react-on-rails/src/tanstack-router/serverRender.ts
  • packages/react-on-rails/src/tanstack-router/types.ts
  • packages/react-on-rails/src/types/index.ts
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
  • packages/react-on-rails/tests/tanstackRouter.test.ts
  • react_on_rails/lib/react_on_rails/helper.rb
  • react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-on-rails/src/tanstack-router/clientHydrate.ts

Comment thread docs/api-reference/view-helpers-api.md Outdated
Comment thread docs/building-features/tanstack-router.md
Comment thread packages/react-on-rails/src/tanstack-router/serverRender.ts Outdated
Comment thread react_on_rails/lib/react_on_rails/helper.rb
Comment thread react_on_rails/lib/react_on_rails/helper.rb Outdated
Comment thread react_on_rails/spec/dummy/client/app/startup/TanStackRouterApp.jsx Outdated
"Pass props as a Hash, not #{class_name}."
end

render_options.set_option(:props, merge_client_props(existing_props, client_props))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

client_props keys are trusted data from the Node renderer — clarify the trust boundary

client_props values flow from the JS server render result directly into the JSON hydration payload sent to the client. If client_props contains a key like authenticity_token or current_user, it would override the matching prop silently.

Consider adding a safelist of allowed keys (or at minimum a denylist of reserved keys), or at least call out the trust model in a comment so future maintainers know not to tighten / loosen it without understanding the security implications:

# client_props originates from the JS server renderer output. It is treated as
# trusted server-controlled data; do not pass user-controlled values here.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 8, 2026

Review summary — all details are in the inline comments above.

Bugs: (1) clientHydrate.ts line 121: router.ssr !== true should be !router.ssr to handle the object-valued case without dropping manifest data. (2) clientHydrate.ts line 127: setTanStackSsrGlobal is a DOM write in the render body — move to useLayoutEffect to be safe under React 18 Strict Mode double-invoke.

API clarity: TanStackRouterAppAsync.jsx manually re-implements the server branch of createTanStackRouterRenderFunction. The example should just be const TanStackRouterAppAsync = createTanStackRouterRenderFunction(options, deps).

Fragility: dehydrateSsrMatchId mirrors a TanStack Router private wire format. Document the upstream file and version range. The write-guard in enableRouterSsrMode is unreliable in non-strict-mode JS; simplify to just the assignment with a comment.

Nits: ignoreDependencies: [] in knip.ts is a no-op. A comment on merge_server_rendered_client_props! clarifying client_props is server-controlled (not user-input) would help future maintainers.

What is solid: isServerRenderHash refactor, async promise dispatch to processServerRenderHash, Ruby merge_client_props key normalization with clear errors, and strong edge-case test coverage.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86da107bac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +107 to +110
if (hasSsrRouter) {
// RouterClient will hydrate the route matches from window.$_TSR.
} else if (hasDehydratedRouter && typeof router.hydrate === 'function') {
router.hydrate(dehydratedState.dehydratedRouter);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Hydrate router when RouterClient is absent

serverRenderTanStackAppAsync always sends an ssrRouter payload, so this if (hasSsrRouter) branch runs on every SSR hydration and skips router.hydrate(...). In the documented/default setup where RouterClient is not provided, the code later renders RouterProvider, which does not consume window.$_TSR, so the dehydrated server state is never applied; initial route loader state is lost and clients can re-fetch on mount (or render from an unhydrated router state), causing hydration inconsistencies and duplicate data loads.

Useful? React with 👍 / 👎.

@justin808 justin808 merged commit d9e5d7b into master Mar 9, 2026
52 checks passed
@justin808 justin808 deleted the jg/tanstack-query-support branch March 9, 2026 00:37
Seifeldin7 added a commit to Seifeldin7/react_on_rails that referenced this pull request Apr 3, 2026
RouterClient wraps RouterProvider in <Await> which always suspends on
first render (defer() starts with status 'pending', resolves on next
microtask). Since the server renders with RouterProvider directly (no
<Await> wrapper), the structural difference causes React hydration
mismatch errors: "Hydration failed because the initial UI does not
match what was rendered on the server."

React recovers by discarding the server HTML and re-rendering on the
client, but this defeats the performance benefit of SSR.

Fix: use RouterProvider directly on the client (matching the server
tree) and synchronously inject route matches via __store.setState()
+ router.ssr flag — the same approach used by the open-source
react-on-rails tanstack-router integration (commit 15410e2).

The RouterClient parameter is kept in the function signature for
backward compatibility but is intentionally unused.

Ref: shakacode#2516

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Backlog priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documentation: Add TanStack Router Integration Guide

1 participant