Skip to content

Conversation

@roduyemi
Copy link
Contributor

@roduyemi roduyemi commented Oct 29, 2025

Summary by CodeRabbit

  • New Features

    • Added a not-found "via-head" route and navigation link in both React and Solid examples.
  • Bug Fixes

    • SSR hydration now records/logs NotFound per-route and handles per-match hydration errors locally without crashing.
  • Tests

    • Expanded not-found tests to exercise head-stage lifecycle and added whitelist for hydration NotFound noise.
    • Added SSR hydration test suites covering adapter initialization, match hydration, and error handling.
  • Chores

    • Excluded the new route from prerendering.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

Adds a new file route /not-found/via-head for React and Solid (its head throws notFound()), updates generated route trees and not-found UIs, extends e2e tests and prerender excludes, and changes SSR client hydration to compute per-match contexts/assets and handle NotFound errors locally.

Changes

Cohort / File(s) Summary
React route tree & files
e2e/react-start/basic/src/routeTree.gen.ts, e2e/react-start/basic/src/routes/not-found/via-head.tsx, e2e/react-start/basic/src/routes/not-found/index.tsx
Add /not-found/via-head file route (Route with head that throws notFound(), component, and notFoundComponent); register route in generated route tree and add a Link in not-found index.
React tests & config
e2e/react-start/basic/tests/not-found.spec.ts, e2e/react-start/basic/vite.config.ts
Extend not-found tests to include head stage (navigation and direct visit), add whitelist entry for NotFound hydration logs, and add /not-found/via-head to prerender exclude list.
Solid route tree & files
e2e/solid-start/basic/src/routeTree.gen.ts, e2e/solid-start/basic/src/routes/not-found/via-head.tsx, e2e/solid-start/basic/src/routes/not-found/index.tsx
Add /not-found/via-head file route for Solid (head throws notFound()), register it in generated route tree and add a Link in not-found index.
Solid tests & config
e2e/solid-start/basic/tests/not-found.spec.ts, e2e/solid-start/basic/vite.config.ts
Extend tests to include head stage and whitelist NotFound hydration logs; add /not-found/via-head to prerender exclusion.
Core SSR hydration client
packages/router-core/src/ssr/ssr-client.ts
Wrap per-match hydration in try/catch; import isNotFound; compute per-route __routeContext and merge with parent/beforeLoad contexts; build assetContext to evaluate head/scripts and assign meta/links/scripts/styles to match; mark notFound() as NotFound on match (log) and rethrow other errors.
Hydration tests (React & Solid)
packages/react-router/tests/hydrate.test.ts, packages/solid-router/tests/hydrate.test.ts
New/expanded SSR hydration tests: adapter initialization, manifest propagation, per-match hydration behavior, serialization adapter handling, and NotFound error handling during hydration.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Hydrator as SSR Hydrator
    participant Route
    participant Logger

    Client->>Hydrator: Begin hydration (window.$_TSR)
    loop per match
        Hydrator->>Route: compute parentContext & __routeContext (route.context)
        Hydrator->>Route: build assetContext (matches, params, loaderData, etc.)
        Hydrator->>Route: evaluate head/scripts with assetContext
        alt head/scripts success
            Route-->>Hydrator: head/meta/scripts/styles
            Hydrator->>Hydrator: assign meta/links/scripts/styles to match
        else notFound() thrown
            Route-->>Hydrator: throw notFound()
            Hydrator->>Hydrator: catch NotFound -> mark match.error = isNotFound
            Hydrator->>Logger: console.error("NotFound during hydration", ...)
            Note right of Hydrator: continue to next match
        else other error thrown
            Route-->>Hydrator: throw Error
            Hydrator->>Hydrator: catch Error -> set match.error
            Hydrator->>Logger: console.error("Error during hydration", ...)
            Hydrator->>Client: rethrow (abort)
        end
    end
    Hydrator-->>Client: Hydration complete (matches populated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review focus:
    • packages/router-core/src/ssr/ssr-client.ts — per-match try/catch, isNotFound handling, merge order of contexts, and asset/head/scripts evaluation.
    • New hydration tests (packages/react-router/tests/hydrate.test.ts, packages/solid-router/tests/hydrate.test.ts) — verify mocks/spies and assertions.
    • Generated routeTree.gen.ts updates for React and Solid — verify ids/paths, parent links, and type augmentations.
    • E2E route files (via-head.tsx) — ensure head behavior and test data-testid strings match tests.

Possibly related PRs

Suggested labels

ssr

Suggested reviewers

  • schiller-manuel

Poem

🐰 A little route hopped into view,
It raised its head and called "not you",
SSR caught the tumble, marked the spot,
Tests cheered softly — the tree gained a dot.
Hop on, hydrate, nibble a carrot! 🥕

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 title accurately describes the main change: catching errors during hydration in router-core. All significant changes follow this theme.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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

🧹 Nitpick comments (3)
packages/router-core/tests/hydrate.test.ts (3)

89-93: Consider more complete mock matches.

The mock matches use a minimal structure with only id, routeId, index, and _nonReactive. While this may be sufficient for the serialization adapter test, consider whether additional properties (e.g., params, loaderDeps, context, abortController) are needed to accurately represent real matches and avoid potential runtime errors if the hydrate function accesses these properties.


215-254: LGTM with suggestions for enhanced coverage.

The test correctly validates that errors thrown during route context hydration (specifically from the head hook) are caught and logged to console.error rather than propagating. This aligns with the PR objective.

Consider adding assertions to verify that:

  1. The hydrate function completes successfully despite the error (doesn't reject the promise)
  2. Other matches continue to be processed when one match throws an error

13-255: Consider expanding test coverage for additional hydration scenarios.

The current tests provide good coverage of core hydration mechanics and error handling. Consider adding tests for:

  1. CSP nonce meta tag handling
  2. pendingMinMs and _forcePending logic for pending matches
  3. SPA mode detection (lastMatchId mismatch)
  4. Custom router.options.hydrate callback invocation
  5. Route chunk loading with loadRouteChunk

These scenarios are exercised in the hydrate implementation but not currently validated by tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ec3881 and 2e96440.

📒 Files selected for processing (11)
  • e2e/react-start/basic/src/routeTree.gen.ts (10 hunks)
  • e2e/react-start/basic/src/routes/not-found/index.tsx (1 hunks)
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx (1 hunks)
  • e2e/react-start/basic/tests/not-found.spec.ts (3 hunks)
  • e2e/react-start/basic/vite.config.ts (1 hunks)
  • e2e/solid-start/basic/src/routeTree.gen.ts (10 hunks)
  • e2e/solid-start/basic/src/routes/not-found/index.tsx (1 hunks)
  • e2e/solid-start/basic/src/routes/not-found/via-head.tsx (1 hunks)
  • e2e/solid-start/basic/tests/not-found.spec.ts (3 hunks)
  • packages/router-core/src/ssr/ssr-client.ts (1 hunks)
  • packages/router-core/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • e2e/react-start/basic/src/routes/not-found/index.tsx
  • packages/router-core/src/ssr/ssr-client.ts
  • e2e/react-start/basic/vite.config.ts
  • e2e/react-start/basic/tests/not-found.spec.ts
  • e2e/solid-start/basic/src/routes/not-found/index.tsx
  • packages/router-core/tests/hydrate.test.ts
  • e2e/solid-start/basic/tests/not-found.spec.ts
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
  • e2e/solid-start/basic/src/routeTree.gen.ts
  • e2e/solid-start/basic/src/routes/not-found/via-head.tsx
  • e2e/react-start/basic/src/routeTree.gen.ts
**/src/routes/**

📄 CodeRabbit inference engine (AGENTS.md)

Place file-based routes under src/routes/ directories

Files:

  • e2e/react-start/basic/src/routes/not-found/index.tsx
  • e2e/solid-start/basic/src/routes/not-found/index.tsx
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
  • e2e/solid-start/basic/src/routes/not-found/via-head.tsx
e2e/**

📄 CodeRabbit inference engine (AGENTS.md)

Store end-to-end tests under the e2e/ directory

Files:

  • e2e/react-start/basic/src/routes/not-found/index.tsx
  • e2e/react-start/basic/vite.config.ts
  • e2e/react-start/basic/tests/not-found.spec.ts
  • e2e/solid-start/basic/src/routes/not-found/index.tsx
  • e2e/solid-start/basic/tests/not-found.spec.ts
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
  • e2e/solid-start/basic/src/routeTree.gen.ts
  • e2e/solid-start/basic/src/routes/not-found/via-head.tsx
  • e2e/react-start/basic/src/routeTree.gen.ts
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep framework-agnostic core router logic in packages/router-core/

Files:

  • packages/router-core/src/ssr/ssr-client.ts
  • packages/router-core/tests/hydrate.test.ts
🧬 Code graph analysis (5)
e2e/react-start/basic/src/routes/not-found/index.tsx (2)
e2e/react-start/basic/src/routes/not-found/via-head.tsx (1)
  • Route (3-15)
e2e/react-start/basic/src/routes/not-found/route.tsx (1)
  • Route (4-8)
e2e/solid-start/basic/src/routes/not-found/index.tsx (3)
e2e/react-start/basic/src/routes/not-found/index.tsx (1)
  • Route (3-41)
e2e/react-start/basic/src/routes/not-found/via-head.tsx (1)
  • Route (3-15)
e2e/solid-start/basic/src/routes/not-found/via-head.tsx (1)
  • Route (3-15)
packages/router-core/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (2)
  • TsrSsrGlobal (17-28)
  • hydrate (59-279)
e2e/react-start/basic/src/routes/not-found/via-head.tsx (2)
e2e/react-start/basic/src/routes/not-found/index.tsx (1)
  • Route (3-41)
e2e/solid-start/basic/src/routes/not-found/via-head.tsx (1)
  • Route (3-15)
e2e/solid-start/basic/src/routes/not-found/via-head.tsx (1)
e2e/solid-start/basic/src/routes/not-found/index.tsx (1)
  • Route (3-41)
🔇 Additional comments (21)
e2e/react-start/basic/vite.config.ts (1)

23-23: LGTM!

The addition of /not-found/via-head to the prerender exclusion list is correct and consistent with other not-found routes. Routes that trigger notFound() should not be prerendered.

e2e/solid-start/basic/src/routes/not-found/index.tsx (1)

28-37: LGTM!

The via-head link addition follows the established pattern and uses proper Solid.js syntax (class, signal accessor preload()).

e2e/react-start/basic/src/routes/not-found/index.tsx (1)

28-37: LGTM!

The via-head link addition follows the established pattern and uses proper React syntax.

e2e/solid-start/basic/tests/not-found.spec.ts (2)

12-12: LGTM!

The whitelisted error for route context hydration aligns with the PR's objective to improve SSR hydration error handling when notFound() is thrown.


29-29: LGTM!

Expanding the test matrices to include 'head' provides coverage for the new /not-found/via-head route that triggers notFound() during the head phase.

Also applies to: 60-60

e2e/react-start/basic/tests/not-found.spec.ts (1)

12-12: LGTM!

Test updates mirror the Solid implementation and properly extend coverage for the new via-head route with appropriate error whitelisting.

Also applies to: 29-29, 61-61

e2e/react-start/basic/src/routes/not-found/via-head.tsx (2)

3-15: Route correctly implements head-phase notFound() for testing.

The route structure properly tests the scenario where notFound() is thrown during the head phase. The notFoundComponent will be rendered instead of the regular component.


17-23: RouteComponent exists for completeness.

While RouteComponent won't render in practice (since head() always throws notFound()), it's appropriate to define it for test completeness. The data-server attribute helps verify SSR/hydration behavior in tests.

e2e/solid-start/basic/src/routes/not-found/via-head.tsx (1)

1-23: LGTM!

The Solid implementation correctly mirrors the React version with appropriate framework-specific imports. The route properly tests notFound() thrown during the head phase.

e2e/solid-start/basic/src/routeTree.gen.ts (1)

35-35: LGTM!

The generated route tree correctly integrates the new NotFoundViaHeadRoute with proper imports, type definitions, and route registrations throughout the file.

Also applies to: 167-171, 261-261, 295-295, 335-335, 375-375, 409-409, 448-448, 655-661, 765-765, 772-772

e2e/react-start/basic/src/routeTree.gen.ts (5)

173-177: Route constant definition is consistent.

id and path use '/via-head' and parent is NotFoundRouteRoute, matching sibling routes.


281-282: Types and public route maps updated coherently.

Additions for '/not-found/via-head' are present and consistent across FileRoutesByFullPath, FileRoutesByTo, FileRoutesById, and FileRouteTypes unions.

Also applies to: 317-318, 358-359, 401-402, 437-438, 477-478


688-694: Module augmentation entry matches generator conventions.

fullPath, path, preLoaderRoute, and parentRoute align with the new route; no discrepancies spotted.


819-820: Children wiring under NotFoundRouteRoute is correct.

NotFoundViaHeadRoute added to both the interface and value object. Mirrors sibling route patterns.

Also applies to: 826-827


37-37: Import and route wiring verified successfully.

The source file e2e/react-start/basic/src/routes/not-found/via-head.tsx exists and correctly exports Route with a head hook that calls notFound(). The import in routeTree.gen.ts is correctly wired.

packages/router-core/tests/hydrate.test.ts (6)

54-58: LGTM!

The test correctly validates the error handling when window.$_TSR is missing.


60-72: LGTM!

The test correctly validates the error handling when window.$_TSR.router is missing.


118-138: LGTM!

The test correctly validates that no transformer map is created when serialization adapters are empty, while initialization still completes.


140-160: LGTM!

The test correctly validates that the manifest is propagated from window.$_TSR.router into router.ssr.


162-213: LGTM!

The test correctly validates that matches are hydrated with server-provided loader data and status flags, including the case where some matches lack dehydrated data.


4-10: I need to examine what this test file is actually testing to determine if importing from react-router is correct.

The review comment is incorrect and should be disregarded.

The utilities being imported (createMemoryHistory, createRootRoute, createRoute, createRouter, notFound) are framework-specific APIs that belong in @tanstack/react-router, not packages/router-core. These are standard React Router imports, not router-core utilities. Only notFound has a framework-agnostic version in router-core; the others are inherently React-specific and cannot be imported from router-core because they don't exist there.

If the concern is about framework coupling in router-core tests, that's a separate architectural question worth discussing, but the specific directive to import from packages/router-core/src is not actionable because these utilities do not and should not exist there by design.

Likely an incorrect or invalid review comment.


const routeTree = rootRoute.addChildren([indexRoute.addChildren([otherRoute])])

mockRouter = createRouter({ routeTree, history, isServer: true })
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 | 🟡 Minor

Verify the isServer: true flag for client hydration tests.

The hydrate function is designed for client-side hydration, yet the router is created with isServer: true. This contradicts the test scenario and may affect router behavior. Consider using isServer: false or omitting the flag entirely to accurately simulate the client environment.

🤖 Prompt for AI Agents
In packages/router-core/tests/hydrate.test.ts around line 46, the test creates
the router with isServer: true which contradicts the client-side hydration
scenario; update the router instantiation to use isServer: false (or remove the
flag) so the router is created as a client environment, ensuring hydrate runs
against client-mode behavior and any server-only branches are not triggered.

@nx-cloud
Copy link

nx-cloud bot commented Oct 29, 2025

View your CI Pipeline Execution ↗ for commit dad79fe

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

☁️ Nx Cloud last updated this comment at 2025-11-19 20:00:57 UTC

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 (2)
packages/router-core/src/ssr/ssr-client.ts (1)

220-222: Error handling concern already raised in previous review.

The indiscriminate error swallowing in this catch handler has been comprehensively flagged in the previous review, including concerns about silent failures, lack of state tracking, and inconsistent router state.

packages/router-core/tests/hydrate.test.ts (1)

48-48: isServer flag concern already raised in previous review.

The use of isServer: true for client-side hydration tests has been flagged in the previous review as contradicting the test scenario.

🧹 Nitpick comments (2)
packages/router-core/tests/hydrate.test.ts (2)

1-11: Consider importing from source instead of dist.

The import on lines 4-10 uses ../../react-router/dist/esm, which is unusual for unit tests. Typically, tests import from source files to ensure they're testing the actual implementation rather than the built artifacts.

Additionally, ESLint flags import ordering issues that could be addressed.

Apply this diff to import from source and fix ordering:

-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import {
+  createMemoryHistory,
+  createRootRoute,
+  createRoute,
+  createRouter,
+  notFound,
+} from '../../react-router/src'
 import { hydrate } from '../src/ssr/ssr-client'
 import type { TsrSsrGlobal } from '../src/ssr/ssr-client'
-import {
-  createMemoryHistory,
-  createRootRoute,
-  createRoute,
-  createRouter,
-  notFound,
-} from '../../react-router/dist/esm'
 import type { AnyRouteMatch } from '../src'

217-259: Consider expanding error handling test coverage.

The test validates that notFound() errors during hydration are caught and logged. However, it only covers one error type and doesn't verify:

  1. Router state remains consistent after the error
  2. Hydration completes successfully despite the error
  3. Other error types (e.g., TypeError, ReferenceError) are handled similarly

Consider adding test cases for:

it('should handle unexpected errors during route context hydration', async () => {
  // Test TypeError, ReferenceError, etc.
})

it('should continue hydration after error in one match', async () => {
  // Verify that other matches complete hydration successfully
})

Additionally, verify that the router state is checked after error handling to ensure consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e96440 and f9ee4a5.

📒 Files selected for processing (4)
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx (1 hunks)
  • e2e/solid-start/basic/src/routes/not-found/via-head.tsx (1 hunks)
  • packages/router-core/src/ssr/ssr-client.ts (1 hunks)
  • packages/router-core/tests/hydrate.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/solid-start/basic/src/routes/not-found/via-head.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/router-core/tests/hydrate.test.ts
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
  • packages/router-core/src/ssr/ssr-client.ts
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep framework-agnostic core router logic in packages/router-core/

Files:

  • packages/router-core/tests/hydrate.test.ts
  • packages/router-core/src/ssr/ssr-client.ts
**/src/routes/**

📄 CodeRabbit inference engine (AGENTS.md)

Place file-based routes under src/routes/ directories

Files:

  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
e2e/**

📄 CodeRabbit inference engine (AGENTS.md)

Store end-to-end tests under the e2e/ directory

Files:

  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
PR: TanStack/router#0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/router-core/** : Keep framework-agnostic core router logic in packages/router-core/
Learnt from: CR
PR: TanStack/router#0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/{react-router,solid-router}/** : Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
PR: TanStack/router#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
  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
  • packages/router-core/src/ssr/ssr-client.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
PR: TanStack/router#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
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
PR: TanStack/router#0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/router-core/** : Keep framework-agnostic core router logic in packages/router-core/

Applied to files:

  • packages/router-core/tests/hydrate.test.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
PR: TanStack/router#5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
PR: TanStack/router#0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/{react-router,solid-router}/** : Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Applied to files:

  • e2e/react-start/basic/src/routes/not-found/via-head.tsx
📚 Learning: 2025-09-28T21:41:45.233Z
Learnt from: nlynzaad
PR: TanStack/router#5284
File: e2e/react-start/basic/server.js:50-0
Timestamp: 2025-09-28T21:41:45.233Z
Learning: In Express v5, catch-all routes must use named wildcards. Use `/*splat` to match everything except root path, or `/{*splat}` (with braces) to match including root path. The old `*` syntax is not allowed and will cause "Missing parameter name" errors. This breaking change requires explicit naming of wildcard parameters.

Applied to files:

  • packages/router-core/src/ssr/ssr-client.ts
🧬 Code graph analysis (2)
packages/router-core/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (2)
  • TsrSsrGlobal (17-28)
  • hydrate (59-279)
e2e/react-start/basic/src/routes/not-found/via-head.tsx (1)
e2e/react-start/basic/src/routes/not-found/index.tsx (1)
  • Route (3-41)
🪛 ESLint
packages/router-core/tests/hydrate.test.ts

[error] 1-1: Member 'expect' of the import declaration should be sorted alphabetically.

(sort-imports)


[error] 4-10: ../../react-router/dist/esm import should occur before type import of ../src/ssr/ssr-client

(import/order)

🔇 Additional comments (4)
packages/router-core/tests/hydrate.test.ts (1)

56-215: Comprehensive test coverage for core hydration scenarios.

These tests effectively cover:

  • Missing bootstrap data validation
  • Serialization adapter initialization and buffer execution
  • Manifest propagation to router.ssr
  • Match hydration with server-provided data

The test structure is clear and assertions are appropriate.

e2e/react-start/basic/src/routes/not-found/via-head.tsx (3)

1-1: LGTM!

The imports are correct and appropriate for creating a file-based route that tests notFound() behavior.


17-23: LGTM!

The RouteComponent is well-implemented with appropriate test attributes. The data-server={typeof window} is a clever way to distinguish between server and client rendering contexts. While this component should not actually render (since the head hook always throws notFound()), it serves as a valid test fixture to ensure the notFoundComponent takes precedence.


3-15: Implementation correctly matches all test expectations.

The route implementation is correct:

  • The head hook throws notFound() immediately, preventing RouteComponent from rendering
  • The notFoundComponent has data-testid="via-head-notFound-component" matching test expectations for visibility
  • The RouteComponent has data-testid="via-head-route-component" matching test expectations for non-visibility
  • Both navigation and direct visit scenarios in the test suite will pass
  • The hydration error 'Error during route context hydration: {isNotFound: true}' is properly whitelisted in the tests

@roduyemi
Copy link
Contributor Author

Hi @schiller-manuel, do you need me to do anything more for this PR to merge? 🙏

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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/router-core/tests/hydrate.test.ts (1)

18-49: Re‑check isServer: true for a client‑side hydrate test setup.

The router used for hydrate is created with isServer: true. Since hydrate is the client‑side hydration entry, it may be more representative to run these tests with a client‑mode router (isServer: false or omitting the flag), unless there is a specific reason to exercise the server branch here. Please confirm this is intentional and not masking any client‑only behavior.

🧹 Nitpick comments (1)
packages/router-core/tests/hydrate.test.ts (1)

217-261: Consider also asserting on match.error for stronger NotFound guarantees.

This test nicely verifies that notFound() thrown from head is caught and logged with the expected message and an error carrying isNotFound: true. To fully lock in the new behavior, you could additionally assert that the match’s error was normalized:

const match = mockRouter.state.matches[0] as AnyRouteMatch
expect(match.error).toEqual({ isNotFound: true })

That would protect both the logging and match.error contract of the NotFound hydration path.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 492a898 and 0043a5c.

📒 Files selected for processing (1)
  • packages/router-core/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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
🧬 Code graph analysis (1)
packages/router-core/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (2)
  • TsrSsrGlobal (19-34)
  • hydrate (65-301)
🔇 Additional comments (2)
packages/router-core/tests/hydrate.test.ts (2)

56-140: Good coverage of bootstrap invariants and serialization adapter paths.

The tests around missing window.$_TSR, missing window.$_TSR.router, and adapter initialization accurately mirror the hydrate invariants and adapter logic (including the t map, buffer flushing, and initialized flag). The use of stubs for matchRoutes/state.matches keeps these tests focused on the intended behavior without over‑mocking the router internals.


164-215: Match hydration assertions align well with hydrateMatch behavior.

The should hydrate matches test correctly verifies that dehydrated match fields (loaderData, status, ssr) are restored onto the client matches via hydrateMatch, using realistic i/l/s/ssr/u shapes and the router’s matches pipeline. This is a solid regression check for the core SSR rehydration path.

@matclayton
Copy link

matclayton commented Nov 19, 2025

I believe this would also solve #5427 , #5546 and #5322

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 19, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: dad79fe

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

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

186-207: Align context reconstruction with core router semantics for consistency

The re-run of route.options.context here is meant to rebuild route context after __beforeLoadContext hydration, but it currently diverges from the core router implementation:

  • parentContext does not fall back to {} when both parentMatch?.context and router.options.context are undefined, unlike the logic in router.matchRoutes. (app.unpkg.com)
  • The contextFnContext omits search, which is included in the main router’s RouteContextOptions when invoking route.options.context. (app.unpkg.com)
  • context passed into route.options.context uses only parentContext, not the already-merged match.context that includes __routeContext and __beforeLoadContext like router.ts does.

For behavioral parity and fewer subtle bugs, consider reshaping this block to follow the same pattern as router.matchRoutes, roughly:

-        const parentMatch = router.state.matches[match.index - 1]
-        const parentContext = parentMatch?.context ?? router.options.context
+        const parentMatch = router.state.matches[match.index - 1]
+        const parentMatchId = parentMatch?.id
+        const parentContext = !parentMatchId
+          ? ((router.options.context as any) ?? {})
+          : (parentMatch?.context ?? router.options.context ?? {})
...
-        if (route.options.context) {
-          const contextFnContext: RouteContextOptions<any, any, any, any> = {
-            deps: match.loaderDeps,
-            params: match.params,
-            context: parentContext ?? {},
-            location: router.state.location,
-            navigate: (opts: any) =>
-              router.navigate({
-                ...opts,
-                _fromLocation: router.state.location,
-              }),
-            buildLocation: router.buildLocation,
-            cause: match.cause,
-            abortController: match.abortController,
-            preload: false,
-            matches,
-          }
-          match.__routeContext =
-            route.options.context(contextFnContext) ?? undefined
-        }
+        // Rebuild route context using the same semantics as in matchRoutes
+        const baseContext = {
+          ...(parentContext ?? {}),
+          ...(match.__routeContext ?? {}),
+          ...(match.__beforeLoadContext ?? {}),
+        }
+
+        if (route.options.context) {
+          const contextFnContext: RouteContextOptions<any, any, any, any> = {
+            search: match.search,
+            params: match.params,
+            context: baseContext,
+            location: router.state.location,
+            navigate: (opts: any) =>
+              router.navigate({
+                ...opts,
+                _fromLocation: router.state.location,
+              }),
+            buildLocation: router.buildLocation,
+            cause: match.cause,
+            abortController: match.abortController,
+            preload: false,
+            matches,
+            deps: match.loaderDeps,
+          }
+          match.__routeContext =
+            route.options.context?.(contextFnContext) ?? {}
+        }

(Exact fields/types can be tuned to match current core APIs.)

This keeps SSR hydration’s context behavior in lockstep with the main router’s route-matching pipeline.


234-246: Preserve full NotFound error object instead of replacing with { isNotFound: true }

In the NotFound branch:

if (isNotFound(err)) {
  match.error = { isNotFound: true }
  // ...
}

you discard useful properties from the original NotFoundError (routeId, data, headers, etc.), which other parts of the system (e.g. CatchNotFound fallbacks that inspect error.routeId) may rely on. (app.unpkg.com)

To keep behavior consistent with the rest of the router, consider:

-        if (isNotFound(err)) {
-          match.error = { isNotFound: true }
+        if (isNotFound(err)) {
+          match.error = err as any
           console.error(
             `NotFound error during hydration for routeId: ${match.routeId}`,
             err,
           )

This still tags the error as not-found (via its isNotFound flag) but preserves any additional metadata.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdf1237 and 8f450d5.

📒 Files selected for processing (1)
  • packages/router-core/src/ssr/ssr-client.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/src/ssr/ssr-client.ts
📚 Learning: 2025-09-28T21:41:45.233Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5284
File: e2e/react-start/basic/server.js:50-0
Timestamp: 2025-09-28T21:41:45.233Z
Learning: In Express v5, catch-all routes must use named wildcards. Use `/*splat` to match everything except root path, or `/{*splat}` (with braces) to match including root path. The old `*` syntax is not allowed and will cause "Missing parameter name" errors. This breaking change requires explicit naming of wildcard parameters.

Applied to files:

  • packages/router-core/src/ssr/ssr-client.ts
🧬 Code graph analysis (1)
packages/router-core/src/ssr/ssr-client.ts (1)
packages/router-core/src/route.ts (1)
  • RouteContextOptions (1075-1083)
⏰ 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: Test
  • GitHub Check: Preview
🔇 Additional comments (3)
packages/router-core/src/ssr/ssr-client.ts (3)

2-2: Import of isNotFound is appropriate and scoped correctly

The new isNotFound import matches its usage in the per‑match hydration error handling below and keeps SSR‑client logic aligned with core not‑found semantics.


218-233: Per-match head/scripts hydration and error isolation look solid

Running head() and scripts() per match with an asset context of { matches, match, params, loaderData } matches the documented RouteOptions.head / scripts signatures and ensures failures stay scoped to a single match instead of aborting the whole hydration pass. (tanstack.com)

No issues here beyond the context-merging concerns already noted.


212-216: Review comment is factually incorrect about JavaScript runtime behavior

The core claim in the review comment is wrong. Spreading undefined in JavaScript does not throw a TypeError; it produces an empty object {}. This was confirmed by runtime testing.

Additionally, the same spread pattern with potentially undefined values exists throughout the codebase (e.g., router.ts lines 1495-1498 with getParentContext()), and the getParentContext() function itself explicitly returns potentially undefined values (router.ts line 1300). If this were truly a runtime error, router.ts would already be crashing.

The code at ssr-client.ts lines 212-216 works as-is without the suggested nullish coalescing fix.

Likely an incorrect or invalid review comment.

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/router-core/tests/hydrate.test.ts (1)

48-48: Verify the isServer: true flag for client hydration tests.

The router is created with isServer: true, which contradicts the client-side hydration scenario being tested. Consider using isServer: false or omitting this flag to accurately reflect the client environment.

🧹 Nitpick comments (2)
packages/router-core/tests/hydrate.test.ts (2)

217-264: Add test coverage for non-NotFound error rethrow behavior.

The current test verifies NotFound error handling, but the hydrate function also rethrows non-NotFound errors (see ssr-client.ts lines 272-278). Add a test case that verifies when a non-NotFound error is thrown during head/scripts execution, it propagates correctly after being logged.

Example test structure:

it('should rethrow non-NotFound errors during route context hydration', async () => {
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
  const testError = new Error('Unexpected error')
  mockHead.mockImplementation(() => {
    throw testError
  })

  const mockMatches = [
    { id: '/', routeId: '/', index: 0, ssr: true, _nonReactive: {} },
  ]

  mockRouter.matchRoutes = vi.fn().mockReturnValue(mockMatches)
  mockRouter.state.matches = mockMatches

  mockWindow.$_TSR = {
    router: {
      manifest: { routes: {} },
      dehydratedData: {},
      lastMatchId: '/',
      matches: [
        {
          i: '/',
          l: { data: 'test' },
          s: 'success',
          ssr: true,
          u: Date.now(),
        },
      ],
    },
    c: vi.fn(),
    p: vi.fn(),
    buffer: [],
    initialized: false,
  }

  await expect(hydrate(mockRouter)).rejects.toThrow('Unexpected error')
  expect(consoleSpy).toHaveBeenCalledWith(
    'Error during hydration for route /:',
    testError,
  )

  consoleSpy.mockRestore()
})

13-265: Consider expanding test coverage for additional hydration scenarios.

While the existing tests cover core functionality well, several code paths in hydrate remain untested:

  • CSP nonce extraction from meta tags
  • Route chunk loading promises
  • minPendingPromise and _forcePending behavior for pending routes
  • Custom router.options.hydrate callback execution
  • SPA mode detection and special handling
  • Route context reconstruction with parent context merging

Adding tests for these scenarios would improve confidence in the hydration logic, particularly for edge cases like SPA mode transitions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56819ef and b438321.

📒 Files selected for processing (1)
  • packages/router-core/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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
🧬 Code graph analysis (1)
packages/router-core/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (2)
  • TsrSsrGlobal (19-34)
  • hydrate (65-307)
⏰ 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). (3)
  • GitHub Check: autofix
  • GitHub Check: Test
  • GitHub Check: Preview

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/react-router/tests/hydrate.test.ts (1)

48-48: Verify the isServer: true flag for this hydrate test router

Hydration is a client-side concern; creating the router with isServer: true may exercise server-mode behavior and diverge from real client usage unless this is intentional.

-    mockRouter = createRouter({ routeTree, history, isServer: true })
+    mockRouter = createRouter({ routeTree, history })
#!/bin/bash
# Inspect other hydrate tests and router configurations for consistency
rg -n "hydrate\(" packages -S
rg -n "isServer:\s*true" packages/react-router/tests -S
🧹 Nitpick comments (3)
packages/react-router/tests/hydrate.test.ts (3)

18-54: Avoid deleting global.window to keep the test environment reusable

beforeEach overwrites global.window and afterEach deletes it, which can interfere with other suites (especially if Vitest runs with a jsdom environment). Safer is to capture the previous value and restore it instead of always deleting the property.

-  let mockWindow: { $_TSR?: TsrSsrGlobal }
+  let mockWindow: { $_TSR?: TsrSsrGlobal }
+  let originalWindow: any
@@
-  beforeEach(() => {
-    // Reset global window mock
-    mockWindow = {}
-    ;(global as any).window = mockWindow
+  beforeEach(() => {
+    // Reset global window mock and preserve any existing window
+    originalWindow = (global as any).window
+    mockWindow = {}
+    ;(global as any).window = mockWindow
@@
-  afterEach(() => {
-    vi.resetAllMocks()
-    delete (global as any).window
-  })
+  afterEach(() => {
+    vi.resetAllMocks()
+    if (originalWindow !== undefined) {
+      ;(global as any).window = originalWindow
+    } else {
+      delete (global as any).window
+    }
+  })

76-140: Optional: assert buffer clearing / hydrated flag for stronger adapter coverage

If hydrate is expected to clear buffer and/or set window.$_TSR.hydrated = true, adding assertions for that behavior in these adapter tests would tighten regression coverage; otherwise this block looks good.


217-264: Consider loosening the assertion on the exact log message

The NotFound/head test correctly checks that errors are caught, match.error is set, and console.error is invoked. To reduce brittleness if the log text ever changes slightly, you could assert with a partial match (e.g. expect.stringContaining('NotFound error during hydration')) instead of the full literal.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b438321 and 52cb3e0.

📒 Files selected for processing (1)
  • packages/react-router/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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/react-router/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/react-router/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/react-router/tests/hydrate.test.ts
🧬 Code graph analysis (1)
packages/react-router/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (1)
  • TsrSsrGlobal (19-34)
🔇 Additional comments (2)
packages/react-router/tests/hydrate.test.ts (2)

56-75: Negative-path coverage for missing SSR bootstrap data looks solid

The first two tests clearly exercise the failure modes when window.$_TSR or window.$_TSR.router are absent and pin the expected error messages; no issues here.


142-162: Manifest propagation test correctly guards router.ssr.manifest

Asserting that router.ssr mirrors the manifest from window.$_TSR.router.manifest directly targets the SSR bootstrap contract; this test looks accurate and sufficient.

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

♻️ Duplicate comments (1)
packages/react-router/tests/hydrate.test.ts (1)

18-49: Reconsider isServer: true for a client hydration test router

The router used for hydrate is created with isServer: true, even though hydrate represents client-side hydration behavior. This mirrors a concern already raised in the router-core hydrate tests: server-mode configuration may subtly diverge from real client behavior or hit server-only branches.

Unless you’re intentionally validating server-mode hydration, consider dropping the flag or using isServer: false to better reflect the client environment:

-    mockRouter = createRouter({ routeTree, history, isServer: true })
+    mockRouter = createRouter({ routeTree, history })
+    // or: createRouter({ routeTree, history, isServer: false })
🧹 Nitpick comments (2)
packages/react-router/tests/hydrate.test.ts (1)

1-11: Fix import order to satisfy ESLint import/order rule

The static analysis hint is correct: the import from @tanstack/router-core/ssr/client should be grouped with external deps before the internal ../src import.

You can fix this with a small reorder:

-import {
-  createMemoryHistory,
-  createRootRoute,
-  createRoute,
-  createRouter,
-  notFound,
-} from '../src'
-import { hydrate } from '@tanstack/router-core/ssr/client'
-import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client'
+import { hydrate } from '@tanstack/router-core/ssr/client'
+import {
+  createMemoryHistory,
+  createRootRoute,
+  createRoute,
+  createRouter,
+  notFound,
+} from '../src'
+import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client'
 import type { AnyRouteMatch } from '../src'
packages/solid-router/tests/hydrate.test.ts (1)

217-264: Nice NotFound hydration coverage; consider small reuse helper

The NotFound test both marks the match with error.isNotFound and asserts the console.error signature, which tightly guards the new behavior. The repeated construction of mockWindow.$_TSR across tests could optionally be factored into a small helper (e.g., createTsr(routerOverrides)), but that’s purely for readability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 424fe83 and aa6ce57.

📒 Files selected for processing (2)
  • packages/react-router/tests/hydrate.test.ts (1 hunks)
  • packages/solid-router/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 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/solid-router/tests/hydrate.test.ts
  • packages/react-router/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/solid-router/tests/hydrate.test.ts
  • packages/react-router/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/react-router/tests/hydrate.test.ts
🧬 Code graph analysis (2)
packages/solid-router/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (1)
  • TsrSsrGlobal (19-34)
packages/react-router/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (1)
  • TsrSsrGlobal (19-34)
🪛 ESLint
packages/solid-router/tests/hydrate.test.ts

[error] 9-9: @tanstack/router-core/ssr/client import should occur before import of ../src

(import/order)

packages/react-router/tests/hydrate.test.ts

[error] 9-9: @tanstack/router-core/ssr/client import should occur before import of ../src

(import/order)

🔇 Additional comments (4)
packages/react-router/tests/hydrate.test.ts (1)

217-264: Strong coverage of NotFound errors during hydration

This test nicely exercises the new per-match error handling by forcing head to throw notFound(), asserting the error is attached to the match and that a clear console.error log is emitted. It should help prevent regressions where NotFound errors bubble and break hydration.

packages/solid-router/tests/hydrate.test.ts (3)

18-55: Global window mocking and router setup look solid

The beforeEach/afterEach correctly isolate global.window, mocks, and router state per test, which should avoid cross-test leakage.


76-140: Good coverage of serialization adapter initialization

The tests for non-empty vs empty serializationAdapters validate the adapter registry (t map), buffer flushing, and initialized flag behavior in a focused way; this should catch regressions in the SSR client initialization path.


142-215: Match hydration test aligns with dehydrated shape

The dehydratedMatchesAnyRouteMatch expectations for loaderData, status, and ssr look consistent with the compact match format and give good assurance that hydration wires up per-match state correctly.

roduyemi and others added 2 commits November 19, 2025 19:46
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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

🧹 Nitpick comments (1)
packages/solid-router/tests/hydrate.test.ts (1)

1-11: Consider fixing remaining ESLint import violations.

ESLint still flags three formatting issues:

  1. Import order: ../src should come before the type-only import from @tanstack/router-core/ssr/client
  2. Sort order: AnyRouteMatch should be sorted alphabetically within the import list
  3. Type specifier style: Prefer a top-level import type statement instead of inline type AnyRouteMatch

While these don't affect functionality, addressing them would satisfy the linter.

Apply this diff to resolve all three issues:

 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { hydrate } from '@tanstack/router-core/ssr/client'
-import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client'
 import {
+  type AnyRouteMatch,
   createMemoryHistory,
   createRootRoute,
   createRoute,
   createRouter,
   notFound,
-  type AnyRouteMatch,
 } from '../src'
+import { hydrate } from '@tanstack/router-core/ssr/client'
+import type { TsrSsrGlobal } from '@tanstack/router-core/ssr/client'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa6ce57 and c6525e6.

📒 Files selected for processing (2)
  • e2e/solid-start/basic/vite.config.ts (1 hunks)
  • packages/solid-router/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • e2e/solid-start/basic/vite.config.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/solid-router/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/solid-router/tests/hydrate.test.ts
🧬 Code graph analysis (1)
packages/solid-router/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (1)
  • TsrSsrGlobal (19-34)
🪛 ESLint
packages/solid-router/tests/hydrate.test.ts

[error] 4-11: ../src import should occur before type import of @tanstack/router-core/ssr/client

(import/order)


[error] 10-10: Member 'AnyRouteMatch' of the import declaration should be sorted alphabetically.

(sort-imports)


[error] 10-10: Prefer using a top-level type-only import instead of inline type specifiers.

(import/consistent-type-specifier-style)

⏰ 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 (4)
e2e/solid-start/basic/vite.config.ts (1)

23-23: LGTM! Correct exclusion of the new not-found route from prerendering.

The addition of '/not-found/via-head' to the prerender exclusion list is appropriate since this route throws notFound() in its head function. This is consistent with the exclusion of similar routes like /not-found/via-beforeLoad and /not-found/via-loader.

packages/solid-router/tests/hydrate.test.ts (3)

56-215: Excellent test coverage for core hydration scenarios.

These test cases comprehensively validate:

  • Error handling for missing bootstrap data
  • Serialization adapter initialization and mapping
  • Manifest propagation
  • Match hydration with correct data, status, and SSR flags

The test logic is sound, assertions are precise, and mock setup is appropriate for each scenario.


217-264: Strong validation of NotFound error handling during hydration.

This test case directly addresses the PR objective by validating that:

  1. Errors thrown during route context hydration (specifically notFound() from head()) are caught
  2. The error is properly marked on the match as { isNotFound: true }
  3. Diagnostic logging occurs via console.error

The test setup correctly simulates the error condition, and the assertions verify both the error capture and logging behavior. This is a critical test for ensuring graceful error handling during SSR hydration.


48-48: No issue found. The isServer: true flag is correct for hydration tests.

The router is intentionally initialized in server mode because hydration tests simulate the real SSR→client flow: a server-rendered router (with isServer: true) transitions to client-side hydration via the hydrate() function. The tests verify this hydration mechanism works correctly by mocking the dehydrated SSR state and confirming the hydration process succeeds. This setup is consistent across both React and Solid router test suites and is the standard pattern for SSR hydration testing.

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/react-router/tests/hydrate.test.ts (1)

13-55: Solid test harness; reconsider isServer: true for a client hydration scenario.

The in-memory history, route tree, and window mocking look good and isolate the hydrate behavior nicely. The one question mark is:

-    mockRouter = createRouter({ routeTree, history, isServer: true })
+    mockRouter = createRouter({ routeTree, history })
+    // or: createRouter({ routeTree, history, isServer: false })

For SSR client hydration, the router is usually created in client mode; keeping isServer: true may hit server-only branches or subtly change behavior. If the intent is to simulate the browser-side hydrate, it’s worth double‑checking this flag (same concern as noted previously in the core hydrate tests).

🧹 Nitpick comments (2)
packages/solid-router/tests/hydrate.test.ts (2)

164-215: Consider clarifying the partial hydration scenario.

The test sets up two mockMatches but only one dehydratedMatch. While the test correctly verifies that the first match is hydrated, it's unclear whether the second match's behavior (presumably remaining unhydrated) is intentional or incidental. Consider adding an assertion or comment to clarify the expected behavior for routes without corresponding dehydrated data.

Example assertion for the second match:

const secondMatch = mockMatches[1] as AnyRouteMatch
expect(secondMatch.loaderData).toBeUndefined()
expect(secondMatch.ssr).toBeUndefined()

13-265: Optional: Consider additional error scenario coverage.

The current test suite provides excellent coverage of the happy path and NotFound errors. For even more robust error handling, consider adding tests for:

  • Non-NotFound errors thrown during hydration (to verify they're handled differently)
  • Multiple matches encountering errors simultaneously
  • Errors from other route lifecycle hooks beyond head

These additions would further strengthen confidence in the error handling, but may be deferred if they're outside the scope of this PR's NotFound focus.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6525e6 and dad79fe.

📒 Files selected for processing (2)
  • packages/react-router/tests/hydrate.test.ts (1 hunks)
  • packages/solid-router/tests/hydrate.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 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/react-router/tests/hydrate.test.ts
  • packages/solid-router/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/react-router/tests/hydrate.test.ts
  • packages/solid-router/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/react-router/tests/hydrate.test.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/solid-router/tests/hydrate.test.ts
🧬 Code graph analysis (2)
packages/react-router/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (1)
  • TsrSsrGlobal (19-34)
packages/solid-router/tests/hydrate.test.ts (1)
packages/router-core/src/ssr/ssr-client.ts (1)
  • TsrSsrGlobal (19-34)
⏰ 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: Test
  • GitHub Check: Preview
🔇 Additional comments (12)
packages/react-router/tests/hydrate.test.ts (7)

56-60: Good coverage for missing bootstrap data on window.$_TSR.

This test clearly asserts that hydrate fails with the expected error when no SSR bootstrap data is present, which is an important guardrail.


62-74: Good negative test for missing window.$_TSR.router.

The shape of the mocked $ _TSR matches the client contract, and the expectation on the specific error message for a missing router field is precise and valuable.


76-118: Well-structured test for serialization adapter initialization and buffer flushing.

The adapter mock matches the expected interface, the router is wired with serializationAdapters, and the assertions that t is a Map, that it holds fromSerializable under the adapter key, that the buffer callbacks are invoked, and that initialized is set to true collectively validate the key responsibilities of the hydrate path.


120-140: Clear behavior definition for the “no adapters” case.

Verifying that t remains undefined while initialized still flips to true documents the intended contract when no serialization adapters are configured, which will prevent regressions in this edge case.


142-162: Accurate assertion that manifest is surfaced via router.ssr.

Seeding window.$_TSR.router.manifest and asserting that hydrate stores it on mockRouter.ssr.manifest cleanly specifies how SSR metadata should be exposed on the client router. This is concise and correct.


164-215: Good verification that dehydrated match data is applied to route matches.

The test neatly wires matchRoutes, seeds dehydrated matches on $ _TSR.router.matches, and then asserts that the first match receives loaderData, status, and ssr from the dehydrated payload while preserving its id. This gives strong confidence that per-match state hydration behaves as intended.


217-264: Effective test of NotFound handling during route context hydration.

By having head throw notFound(), then asserting that match.error is normalized to { isNotFound: true } and that a descriptive console.error log is emitted with the routeId and error payload, this test nails the new error-handling behavior and ensures NotFound during hydration is captured locally instead of crashing the whole pipeline.

packages/solid-router/tests/hydrate.test.ts (5)

56-74: LGTM! Comprehensive error guard tests.

These tests properly verify that the hydration process fails fast with clear error messages when required bootstrap data is missing. The error messages appropriately guide users to file an issue, which is good for debugging unexpected scenarios.


76-140: LGTM! Thorough serialization adapter tests.

Excellent coverage of both the standard initialization path (with adapters) and the edge case (empty adapters). The tests verify all key aspects: transformer map creation, adapter registration, buffer execution, and the initialized flag. The separation into two distinct test cases makes the intent clear.


142-162: LGTM! Clear manifest propagation test.

This test correctly verifies that the manifest data from the dehydrated router state is properly transferred to the router's SSR property, which is essential for SSR/hydration coordination.


217-264: LGTM! Critical NotFound error handling test.

This test directly addresses the core objective of this PR: catching and handling NotFound errors thrown during hydration. The test comprehensively verifies:

  • Errors thrown from route head functions are caught during hydration
  • The error is properly attached to the match with isNotFound: true
  • Appropriate debugging information is logged to console.error
  • The hydration process continues gracefully instead of crashing

The mock setup and cleanup are handled correctly, ensuring no side effects.


48-48: Need to check the actual test file more carefully to understand the hydrate function and verify the isServer flag usage. Let me search the codebase directly.

Based on my verification through code analysis and web search, I found that the router incorrectly identifies whether it's running on server based on document availability, and isServer can be explicitly set to control this. The test setup with isServer: true is intentionally creating a server-side router context, which is then passed to the hydrate() function to simulate the SSR-to-client transition.

Looking at the SSR documentation, on the client Router defaults to createBrowserHistory, while on the server it uses createMemoryHistory. The tests use createMemoryHistory with isServer: true, which correctly simulates a server-rendered router that will be hydrated on the client. This is the correct pattern for testing hydration functionality—the router is created in server mode, then the hydrate() function processes the dehydrated state for client-side restoration.

The isServer: true flag in the hydration tests is correct and intentional. It properly simulates the SSR server context before client-side hydration occurs. No changes are needed.

@birkskyum birkskyum merged commit 37eb416 into TanStack:main Nov 19, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment