Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Dec 16, 2025

Summary by CodeRabbit

  • Bug Fixes

    • More reliable SSR hydration and streaming completion signaling across frameworks.
    • SSR-injected scripts are now removed from the DOM after execution to avoid leftover nodes.
  • Refactor

    • Reorganized SSR type surface for clearer, shared definitions across packages.
    • Added framework-specific hydration wrappers to unify and standardize post-hydration behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

Walkthrough

Introduces framework-specific hydrateStart wrappers, externalizes SSR types, changes TSR lifecycle to explicit h/e/c signals, switches script cleanup to DOM removal via document.currentScript.remove(), and updates script emission and imports across router and start-client packages.

Changes

Cohort / File(s) Summary
ScriptOnce / script payloads
packages/react-router/src/ScriptOnce.tsx, packages/solid-router/src/ScriptOnce.tsx, packages/vue-router/src/ScriptOnce.tsx
Removed $tsr class attribute and replaced inline $_TSR?.c() payload with document.currentScript.remove() for self-removal.
Framework hydrateStart wrappers & exports
packages/react-start-client/src/hydrateStart.ts, packages/solid-start-client/src/hydrateStart.ts, packages/react-start-client/src/index.tsx, packages/solid-start-client/src/index.tsx, packages/react-start-client/src/StartClient.tsx, packages/solid-start-client/src/hydrateStart.ts
Added local hydrateStart wrappers that await core hydrateStart, call window.$_TSR?.h() on completion, and adjusted StartClient imports/exports to re-export/use the local wrappers.
TSR state machine, types & client/server SSR
packages/router-core/src/ssr/tsrScript.ts, packages/router-core/src/ssr/types.ts, packages/router-core/src/ssr/ssr-server.ts, packages/router-core/src/ssr/ssr-client.ts, packages/router-core/src/ssr/client.ts
Moved SSR-related types into types.ts; added h() and e() signals, renamed streamEndstreamEnded, changed c() to delete state only when hydrated && streamEnded, removed defer logic; server emits GLOBAL_TSR.e() and enqueues document.currentScript.remove() for final scripts; client re-exports types from ./types.
Vue script assembly
packages/vue-router/src/Scripts.tsx
Removed Vue defer script injection and removed $tsr class from barrier script tag.
Start-client surface & types
packages/start-client-core/src/client/index.ts, packages/start-client-core/src/index.tsx, packages/react-start-client/tsconfig.json
Added type re-export from @tanstack/router-core/ssr/client; removed DehydratedRouter re-export from main index; narrowed react-start-client tsconfig includes (removed external startEntry.d.ts).
Tests updated / removed
packages/router-core/tests/hydrate.test.ts, packages/solid-router/tests/hydrate.test.ts
Router tests switched from factory helpers to class constructors and added mock h/e fields; Solid hydrate test file deleted.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor App as Framework App
    participant HS as hydrateStart (wrapper)
    participant Core as start-client-core (core hydrateStart)
    participant Router as Router (instance)
    participant TSR as window.$_TSR
    participant SSR as SSR stream

    App->>HS: call hydrateStart()
    HS->>Core: await core hydrateStart()
    Core->>Router: initialize & hydrate
    Core-->>HS: return Router
    HS->>TSR: h()  -- mark hydrated
    HS-->>App: return Router

    Note over SSR: streaming scripts continue emitting
    SSR->>TSR: e()  -- mark stream ended
    TSR->>TSR: streamEnded = true
    alt hydrated && streamEnded
        TSR->>TSR: c()  -- cleanup & delete window.$_TSR
    end

    SSR->>SSR: final script executes -> document.currentScript.remove()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–75 minutes

  • Attention points:
    • packages/router-core/src/ssr/tsrScript.ts — new h/e/c semantics, ordering and idempotency.
    • packages/router-core/src/ssr/ssr-server.ts — script emission ordering and correct enqueueing of GLOBAL_TSR.e().
    • framework StartClient/hydrateStart wrappers — lifecycle timing across frameworks.
    • tests — removed Solid test file and changes from factory to class usage in router tests.

Possibly related PRs

Suggested reviewers

  • birkskyum

Poem

🐇 I hopped through streams and scripts today,

h() nibbles "hydrated" on the way,
e() waves the stream goodbye,
scripts drop off with a blinked eye,
Tiny paws tidy SSR—hip-hip hooray! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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 title 'fix: generalize hydration cleanup' directly and accurately summarizes the main objective of the pull request, which reorganizes and generalizes the hydration cleanup mechanism across multiple router packages.
✨ 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 generalized-hydration

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a6a678 and 5191c56.

📒 Files selected for processing (1)
  • packages/router-core/tests/hydrate.test.ts (8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/router-core/tests/hydrate.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/router-core/tests/hydrate.test.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Separate framework-agnostic core logic from React/Solid bindings
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/tests/hydrate.test.ts
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.

Applied to files:

  • packages/router-core/tests/hydrate.test.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/tests/hydrate.test.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). (1)
  • GitHub Check: Test
🔇 Additional comments (3)
packages/router-core/tests/hydrate.test.ts (3)

2-5: Test imports updated to reflect new public API surface.

The migration from factory function imports to class-based imports (BaseRootRoute, BaseRoute, RouterCore) and the externalized SSR types path align with the PR objectives. The addition of createMemoryHistory from @tanstack/history is appropriate for creating test fixtures.

Please verify that these class-based exports (BaseRootRoute, BaseRoute, RouterCore) are the intended public API surface going forward, and that the type import path ../src/ssr/types is correct for framework-agnostic SSR types.


23-43: Test suite migrated to class-based API constructors.

The migration from factory functions (createRootRoute, createRoute, createRouter) to class constructors (BaseRootRoute, BaseRoute, RouterCore) is consistently applied. The constructor options match the previous factory function signatures, and all test logic remains intact.

Based on learnings, this aligns with separating framework-agnostic core logic from framework-specific bindings.

Please confirm that the behavior of these class constructors is equivalent to the previous factory functions, and that RouterCore is the appropriate class to use for testing framework-agnostic router functionality.


98-99: Lifecycle signals correctly typed and consistently applied across all test cases.

The TsrSsrGlobal type definition correctly includes h: () => void (hydration complete signal) and e: () => void (stream ended signal) alongside existing c and p signals. All dehydrated state mock objects in the test consistently include these new fields mocked with vi.fn(), enabling proper signal tracking. Type definitions enforce strict TypeScript compliance.


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

@nx-cloud
Copy link

nx-cloud bot commented Dec 16, 2025

View your CI Pipeline Execution ↗ for commit 5191c56

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 8m 41s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2025-12-17 00:14:18 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 16, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6118

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@6118

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6118

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6118

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6118

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6118

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6118

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6118

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6118

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6118

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6118

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6118

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6118

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6118

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6118

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6118

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6118

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6118

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6118

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6118

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@6118

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6118

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6118

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6118

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6118

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6118

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6118

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6118

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6118

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6118

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6118

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6118

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6118

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6118

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6118

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6118

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6118

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6118

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6118

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6118

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6118

commit: 5191c56

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 (1)
packages/solid-start-client/src/hydrateStart.ts (1)

1-12: Duplicate implementation detected.

This implementation is identical to the React version in packages/react-start-client/src/hydrateStart.ts. Please refer to the review comment on that file regarding consolidating these identical wrappers.

🧹 Nitpick comments (4)
packages/react-start-client/src/hydrateStart.ts (2)

1-12: Consider consolidating identical framework wrappers.

The React and Solid implementations of hydrateStart are identical (see packages/solid-start-client/src/hydrateStart.ts). This violates the DRY principle and increases maintenance burden.

Consider extracting this wrapper logic into a shared utility in @tanstack/start-client-core that accepts the framework-specific hydration function, or document why framework-specific wrappers are architecturally necessary despite identical implementations.

Based on learnings, separate framework-agnostic core logic from React/Solid bindings.


10-10: Add type augmentation for window.$_TSR.

The access to window.$_TSR?.h() lacks type safety. Consider adding a type declaration or augmentation to define the $_TSR property on the Window interface.

Example type augmentation:

declare global {
  interface Window {
    $_TSR?: {
      h: () => void
      e: () => void
    }
  }
}
packages/router-core/src/ssr/types.ts (1)

21-40: Document the two-phase completion model in the interface comments.

The interface introduces a sophisticated two-phase lifecycle with h(), e(), and c(), but the comments are brief. Consider expanding the documentation to clarify:

  • When h() vs e() should be called
  • That c() only executes cleanup when both phases complete
  • The purpose of the buffer and p() mechanism

Apply this diff to enhance documentation:

 export interface TsrSsrGlobal {
   router?: DehydratedRouter
-  // Signal that router hydration is complete
+  /**
+   * Signal that router hydration is complete (phase 1).
+   * Must be paired with e() to trigger final cleanup.
+   */
   h: () => void
-  // Signal that stream has ended
+  /**
+   * Signal that SSR stream has ended (phase 2).
+   * Must be paired with h() to trigger final cleanup.
+   */
   e: () => void
-  // Cleanup all hydration resources and scripts
+  /**
+   * Cleanup all hydration resources and scripts.
+   * Only executes when both h() and e() have been called.
+   */
   c: () => void
-  // p: Push script into buffer or execute immediately
+  /**
+   * Push script into buffer or execute immediately.
+   * Scripts are buffered until initialized=true, then executed.
+   */
   p: (script: () => void) => void
packages/router-core/src/ssr/tsrScript.ts (1)

2-15: Consider guarding against repeated lifecycle calls.

The two-phase completion model is correctly implemented: cleanup only occurs when both hydrated and streamEnded are true. However, there are no guards against calling h() or e() multiple times, which could lead to unexpected behavior if lifecycle methods are invoked incorrectly.

Consider adding guards to prevent repeated calls:

 self.$_TSR = {
   h() {
+    if (this.hydrated) return
     this.hydrated = true
     this.c()
   },
   e() {
+    if (this.streamEnded) return
     this.streamEnded = true
     this.c()
   },

This makes the lifecycle more defensive and idempotent.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8628d01 and 59c2ec4.

📒 Files selected for processing (17)
  • packages/react-router/src/ScriptOnce.tsx (1 hunks)
  • packages/react-start-client/src/StartClient.tsx (1 hunks)
  • packages/react-start-client/src/hydrateStart.ts (1 hunks)
  • packages/react-start-client/src/index.tsx (1 hunks)
  • packages/react-start-client/tsconfig.json (1 hunks)
  • packages/router-core/src/ssr/ssr-client.ts (1 hunks)
  • packages/router-core/src/ssr/ssr-server.ts (4 hunks)
  • packages/router-core/src/ssr/tsrScript.ts (1 hunks)
  • packages/router-core/src/ssr/types.ts (1 hunks)
  • packages/solid-router/src/ScriptOnce.tsx (1 hunks)
  • packages/solid-start-client/src/hydrateStart.ts (1 hunks)
  • packages/solid-start-client/src/index.tsx (1 hunks)
  • packages/start-client-core/src/client/index.ts (1 hunks)
  • packages/start-client-core/src/index.tsx (1 hunks)
  • packages/vue-router/src/ScriptOnce.tsx (1 hunks)
  • packages/vue-router/src/Scripts.tsx (0 hunks)
  • packages/vue-start-client/src/StartClient.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/vue-router/src/Scripts.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript strict mode with extensive type safety for all code

Files:

  • packages/react-start-client/src/index.tsx
  • packages/router-core/src/ssr/types.ts
  • packages/solid-start-client/src/hydrateStart.ts
  • packages/react-start-client/src/StartClient.tsx
  • packages/vue-start-client/src/StartClient.tsx
  • packages/router-core/src/ssr/ssr-client.ts
  • packages/start-client-core/src/index.tsx
  • packages/start-client-core/src/client/index.ts
  • packages/solid-start-client/src/index.tsx
  • packages/react-router/src/ScriptOnce.tsx
  • packages/router-core/src/ssr/ssr-server.ts
  • packages/solid-router/src/ScriptOnce.tsx
  • packages/vue-router/src/ScriptOnce.tsx
  • packages/router-core/src/ssr/tsrScript.ts
  • packages/react-start-client/src/hydrateStart.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Implement ESLint rules for router best practices using the ESLint plugin router

Files:

  • packages/react-start-client/src/index.tsx
  • packages/router-core/src/ssr/types.ts
  • packages/solid-start-client/src/hydrateStart.ts
  • packages/react-start-client/src/StartClient.tsx
  • packages/vue-start-client/src/StartClient.tsx
  • packages/router-core/src/ssr/ssr-client.ts
  • packages/start-client-core/src/index.tsx
  • packages/start-client-core/src/client/index.ts
  • packages/solid-start-client/src/index.tsx
  • packages/react-router/src/ScriptOnce.tsx
  • packages/router-core/src/ssr/ssr-server.ts
  • packages/solid-router/src/ScriptOnce.tsx
  • packages/vue-router/src/ScriptOnce.tsx
  • packages/router-core/src/ssr/tsrScript.ts
  • packages/react-start-client/src/hydrateStart.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Separate framework-agnostic core logic from React/Solid bindings
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.

Applied to files:

  • packages/react-start-client/src/index.tsx
  • packages/router-core/src/ssr/types.ts
  • packages/react-start-client/tsconfig.json
  • packages/solid-start-client/src/hydrateStart.ts
  • packages/react-start-client/src/StartClient.tsx
  • packages/vue-start-client/src/StartClient.tsx
  • packages/router-core/src/ssr/ssr-client.ts
  • packages/start-client-core/src/index.tsx
  • packages/start-client-core/src/client/index.ts
  • packages/solid-start-client/src/index.tsx
  • packages/router-core/src/ssr/ssr-server.ts
  • packages/react-start-client/src/hydrateStart.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/ssr/ssr-client.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/ssr/ssr-client.ts
  • packages/start-client-core/src/index.tsx
🧬 Code graph analysis (3)
packages/solid-start-client/src/hydrateStart.ts (1)
packages/react-start-client/src/hydrateStart.ts (1)
  • hydrateStart (7-12)
packages/router-core/src/ssr/ssr-server.ts (1)
packages/router-core/src/ssr/constants.ts (1)
  • GLOBAL_TSR (1-1)
packages/react-start-client/src/hydrateStart.ts (3)
packages/solid-start-client/src/hydrateStart.ts (1)
  • hydrateStart (7-12)
packages/solid-start-client/src/index.tsx (1)
  • hydrateStart (2-2)
packages/start-client-core/src/client/index.ts (1)
  • hydrateStart (1-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). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (18)
packages/vue-router/src/ScriptOnce.tsx (2)

24-41: LGTM: Client-side hydration logic is correct.

The implementation properly handles the hydration mismatch pattern:

  • Renders an empty script with data-allow-mismatch before mounting
  • Returns null after mounting to clean up the placeholder
  • Works in tandem with the server-side self-removing script

This aligns well with Vue's hydration model and the PR's objective of generalizing hydration cleanup.


15-22: Browser compatibility is strong for modern applications.

document.currentScript is well established and works across many devices and browser versions since July 2015. It is not supported in Internet Explorer, but overall cross-browser compatibility score is 92, which is acceptable for most modern applications. If IE support is required, a polyfill would be needed.

The self-removal pattern is sound for SSR hydration. The concatenation approach is appropriate for internally-controlled script content within the framework, where props.children originates from framework logic rather than user input.

packages/vue-start-client/src/StartClient.tsx (1)

13-21: The code in packages/vue-start-client/src/StartClient.tsx is correct and properly typed. Type definitions for window.$_TSR with the h() method already exist in the codebase through the TsrSsrGlobal interface exported from @tanstack/router-core/ssr/client, and the global Window interface augmentation has not been removed. No changes are needed.

Likely an incorrect or invalid review comment.

packages/react-start-client/src/index.tsx (1)

3-3: LGTM!

The addition of hydrateStart to the public API surface aligns well with the introduction of the framework-specific wrapper and maintains consistency with the Solid package.

packages/react-start-client/src/StartClient.tsx (1)

3-3: LGTM!

The import change correctly references the new local wrapper, ensuring the hydration completion signal window.$_TSR?.h() is properly invoked while preserving existing behavior.

packages/solid-start-client/src/index.tsx (1)

2-2: LGTM!

The export change maintains consistency with the React package structure and correctly exposes the Solid-specific hydration wrapper.

packages/react-start-client/tsconfig.json (1)

7-7: The removal of ../start-client-core/src/startEntry.d.ts from react-start-client's tsconfig include does not affect type augmentation for this package. The module augmentation declared in that file is for the internal virtual modules #tanstack-start-entry and #tanstack-router-entry, which are used only by start-client-core's hydrateStart implementation, not by react-start-client. React-start-client properly receives types through the package's exported types, making the direct file inclusion unnecessary.

Likely an incorrect or invalid review comment.

packages/solid-router/src/ScriptOnce.tsx (1)

18-18: LGTM! DOM-based script removal is cleaner.

The change from invoking a global cleanup function to direct DOM removal via document.currentScript.remove() simplifies the hydration lifecycle and eliminates the need for global coordination. This pattern is well-supported across modern browsers.

packages/react-router/src/ScriptOnce.tsx (1)

16-16: LGTM! Consistent with the generalized cleanup approach.

The switch to document.currentScript.remove() is consistent with the Solid router implementation and correctly implements the new DOM-based cleanup pattern.

packages/start-client-core/src/client/index.ts (1)

2-2: LGTM! Type-only export has no runtime impact.

Adding the type re-export from @tanstack/router-core/ssr/client makes SSR-related types publicly available without affecting the runtime bundle. This aligns well with the PR's goal of consolidating SSR type definitions.

packages/router-core/src/ssr/types.ts (1)

4-12: LGTM! Compact serialization format is appropriate for SSR.

The DehydratedMatch interface uses single-letter property names (e.g., i, b, l) to minimize the serialized payload size, which is a good optimization for SSR. The type mappings to MakeRouteMatch properties are clear and complete.

packages/router-core/src/ssr/ssr-server.ts (4)

93-93: LGTM! Script self-removal aligns with framework changes.

The change to append document.currentScript.remove() in the script buffer correctly implements the DOM-based cleanup pattern, consistent with the updates to ScriptOnce components in React and Solid routers.


145-145: LGTM! Simplified script generation.

Removing the class="$tsr" attribute from injected scripts simplifies the HTML output. The script identification is no longer needed since scripts now self-remove via document.currentScript.remove().


225-225: LGTM! Lifecycle method invocation is correct.

Changing from directly setting streamEnd=true to calling .e() properly uses the new lifecycle API, which handles the two-phase cleanup coordination.


249-249: Remove className: '$tsr' from line 249 or clarify its necessity for buffered scripts.

The $tsr class on the buffered script tag (line 249) is used in the cleanup function document.querySelectorAll(".\\$tsr").forEach(e => { e.remove() }) to identify and remove temporary SSR scripts after hydration. If this cleanup mechanism is still required, the class attribute should be retained. However, if dynamic script injection at line 146 no longer relies on this cleanup pattern, verify whether the buffered script still needs this identifier. If not, remove it for consistency with dynamically injected scripts.

packages/router-core/src/ssr/ssr-client.ts (2)

5-10: LGTM! Type consolidation improves maintainability.

The updated imports correctly reference types from the centralized ./types module instead of inline definitions, improving code organization and reducing duplication.


32-278: The framework-specific hydrateStart wrappers correctly invoke window.$_TSR?.h() after hydration.

Verified across all three framework implementations:

  • React: packages/react-start-client/src/hydrateStart.ts calls window.$_TSR?.h() after coreHydrateStart()
  • Solid: packages/solid-start-client/src/hydrateStart.ts calls window.$_TSR?.h() after hydration
  • Vue: packages/vue-start-client/src/StartClient.tsx calls window.$_TSR?.h()

The core hydrate() function in ssr-client.ts correctly does not call h() directly; instead, each framework wrapper handles the lifecycle signal after calling the core hydration logic.

packages/start-client-core/src/index.tsx (1)

1-1: No action needed. The DehydratedRouter type was never exported from @tanstack/start-client-core and is not part of its public API. It remains available for internal use within @tanstack/router-core through the ./ssr/types module. No breaking change is introduced by this line change.

@schiller-manuel schiller-manuel merged commit f330532 into main Dec 17, 2025
6 checks passed
@schiller-manuel schiller-manuel deleted the generalized-hydration branch December 17, 2025 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants