Skip to content

Conversation

@jakst
Copy link
Contributor

@jakst jakst commented Jan 8, 2026

Summary

This PR adds failing tests that reproduce an issue with navigate({ href: '/basepath/...' }) when using a router with a custom basepath.

The Issue

When calling navigate() with an href that already includes the basepath:

  1. Without reloadDocument: The basepath gets doubled (e.g., /app/app/posts instead of /app/posts)
  2. With reloadDocument: true: Works in plain routers (react-router, solid-router), but fails in start frameworks (react-start, solid-start)

Test Results

Test react-router solid-router react-start solid-start
navigate({ href: '/basepath/...' }) FAIL FAIL FAIL FAIL
navigate({ href: '/basepath/...', reloadDocument: true }) PASS PASS FAIL FAIL

Root Cause

The issue was introduced in @tanstack/router-core@1.143.4. In buildAndCommitLocation, when href is provided, it parses the pathname and sets rest.to = parsed.pathname. But to is expected to be without the basepath - the router adds the basepath automatically. So passing href="/app/about" sets to="/app/about", which then becomes /app/app/about.

Files Added

  • e2e/react-router/basepath-file-based/ - test buttons and 2 tests
  • e2e/solid-router/basepath-file-based/ - test buttons and 2 tests
  • e2e/react-start/custom-basepath/ - navigate-test route and 2 tests
  • e2e/solid-start/custom-basepath/ - navigate-test route and 2 tests

Summary by CodeRabbit

  • New Features

    • Buttons/routes now support href-based navigation that respects custom basepaths, including an option to force a full document reload.
  • Bug Fixes

    • Improved URL rewriting so href navigations correctly resolve with configured basepaths and reloads use the rewritten/public URL when appropriate.
  • Tests

    • Added end-to-end tests covering href-based basepath navigation and reloadDocument scenarios across React and Solid apps.
  • Documentation

    • Added examples demonstrating href navigation with custom basepaths and reload options.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

Adds href-based navigation and tests that exercise basepath-aware href handling (with and without full-document reload) across React/ Solid examples and updates router core to rewrite hrefs via the router’s rewrite pipeline before committing locations.

Changes

Cohort / File(s) Summary
React Router basepath example
e2e/react-router/basepath-file-based/src/routes/index.tsx, e2e/react-router/basepath-file-based/tests/reload-document.test.ts
Added href-based navigation buttons (with/without reloadDocument) and E2E tests asserting navigation to /app/about under the basepath.
React Start custom basepath
e2e/react-start/custom-basepath/src/routes/navigate-test.tsx, e2e/react-start/custom-basepath/src/routeTree.gen.ts, e2e/react-start/custom-basepath/tests/navigation.spec.ts
Introduced /navigate-test route component (two href navigation buttons), updated generated route tree/typings to export the route, and added E2E tests verifying navigation to /custom/basepath/posts.
React Start test ID update
e2e/react-start/custom-basepath/src/routes/posts.tsx
Added data-testid="posts-component" to PostsComponent for test assertions.
Solid Router basepath example
e2e/solid-router/basepath-file-based/src/routes/index.tsx, e2e/solid-router/basepath-file-based/tests/reload-document.test.ts
Added href-based navigation buttons (with/without reloadDocument) and tests asserting /app/about navigation respects basepath without duplication.
Solid Start custom basepath
e2e/solid-start/custom-basepath/src/routes/navigate-test.tsx, e2e/solid-start/custom-basepath/src/routeTree.gen.ts, e2e/solid-start/custom-basepath/tests/navigation.spec.ts
Added /navigate-test route and component, updated generated route tree/typings, and added E2E tests for basepath href navigation (with reload variants).
Solid Start test ID update
e2e/solid-start/custom-basepath/src/routes/posts.tsx
Added data-testid="posts-component" to PostsComponent for test assertions.
Router core URL rewrite / navigation
packages/router-core/src/router.ts
When href is provided, rewrites the parsed pathname through the router rewrite pipeline before assigning rest.to; adjusts navigate (reloadDocument branch) to prefer built/publicHref for reload targets and to handle href vs full URL correctly.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as navigate()/click
  participant RouterCore as Router (buildAndCommitLocation)
  participant Rewriter as executeRewriteInput (rewrite pipeline)
  participant LocationBuilder as buildLocation
  participant Browser as Browser/navigation

  Caller->>RouterCore: request navigation (to or href, reloadDocument?)
  RouterCore->>Rewriter: rewrite(parsed.pathname)  -- when href provided
  Rewriter-->>RouterCore: rewritten pathname
  RouterCore->>LocationBuilder: buildLocation(rest.with rewritten pathname)
  LocationBuilder-->>RouterCore: Location (href, publicHref, pathname)
  RouterCore->>Browser: commitLocation / pushState or full reload (uses publicHref when reload)
  Browser-->>Caller: navigation completed / page loaded
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

package: react-router, package: solid-router

Suggested reviewers

  • nlynzaad

Poem

🐇 I hopped through paths both near and far,
With hrefs aligned to basepath's star,
Buttons click, rewrites hum,
Reload or not — onward we run,
Cheers — the routes all find their jar! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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: navigate() with href containing basepath' directly describes the main change: fixing navigate() function behavior when using href with basepath. It is concise, specific, and clearly indicates the primary issue being addressed.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@nx-cloud
Copy link

nx-cloud bot commented Jan 8, 2026

View your CI Pipeline Execution ↗ for commit ce49fb9

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

☁️ Nx Cloud last updated this comment at 2026-01-08 22:30:10 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 8, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6335

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: ce49fb9

@schiller-manuel schiller-manuel changed the title test: add failing tests for navigate() with href containing basepath fix: navigate() with href containing basepath Jan 8, 2026
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/router-core/src/router.ts (1)

2054-2062: Consider using location.publicHref instead of location.url.href.

On lines 2056-2057, both href and publicHref are assigned from location.url.href, which includes the full origin (e.g., http://localhost/app/posts).

For publicHref, it would be more semantically correct to use location.publicHref which contains just the path portion (e.g., /app/posts). This aligns with how publicHref is used elsewhere and avoids potential issues with full URLs in certain environments.

♻️ Suggested change
      if (to !== undefined || !href) {
        const location = this.buildLocation({ to, ...rest } as any)
        href = href ?? location.url.href
-       publicHref = publicHref ?? location.url.href
+       publicHref = publicHref ?? location.publicHref
      }

Please verify whether using location.url.href (full URL with origin) vs location.publicHref (path only) causes any behavioral differences in the failing start framework tests.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f34966 and ce49fb9.

📒 Files selected for processing (1)
  • packages/router-core/src/router.ts
🧰 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/src/router.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/src/router.ts
🧠 Learnings (6)
📓 Common learnings
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.
📚 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/router.ts
📚 Learning: 2025-12-21T12:52:35.231Z
Learnt from: Sheraff
Repo: TanStack/router PR: 6171
File: packages/router-core/src/new-process-route-tree.ts:898-898
Timestamp: 2025-12-21T12:52:35.231Z
Learning: In `packages/router-core/src/new-process-route-tree.ts`, the matching logic intentionally allows paths without trailing slashes to match index routes with trailing slashes (e.g., `/a` can match `/a/` route), but not vice-versa (e.g., `/a/` cannot match `/a` layout route). This is implemented via the condition `!pathIsIndex || node.kind === SEGMENT_TYPE_INDEX` and is a deliberate design decision to provide better UX by being permissive with missing trailing slashes.

Applied to files:

  • packages/router-core/src/router.ts
📚 Learning: 2025-12-17T02:17:55.086Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 6120
File: packages/router-generator/src/generator.ts:654-657
Timestamp: 2025-12-17T02:17:55.086Z
Learning: In `packages/router-generator/src/generator.ts`, pathless_layout routes must receive a `path` property when they have a `cleanedPath`, even though they are non-path routes. This is necessary because child routes inherit the path from their parent, and without this property, child routes would not have the correct full path at runtime.

Applied to files:

  • packages/router-core/src/router.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/router.ts
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router

Applied to files:

  • packages/router-core/src/router.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). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (1)
packages/router-core/src/router.ts (1)

1980-1991: LGTM! Basepath-aware href rewriting.

The logic correctly strips the basepath from the provided href before assigning to rest.to, preventing the basepath from being doubled when buildLocation adds it back. The comment clearly documents the intent.

@schiller-manuel schiller-manuel merged commit a1324bb into TanStack:main Jan 8, 2026
6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 8, 2026
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.

3 participants