Skip to content

Conversation

schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Oct 1, 2025

fixes #5324

Summary by CodeRabbit

  • New Features
    • Added a Redirect section with a landing page linking to a "Throw It" action and a redirect route that navigates to a specific post.
  • Tests
    • Added end-to-end tests for client-side and server-side redirect flows.
    • Added a test identifier to the post view for reliable assertions.
  • Refactor
    • Improved redirect URL handling and normalization for generated redirect responses.

Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Walkthrough

Adds a redirect demo under custom basepath: new /redirect and /redirect/throw-it routes; throw-it throws a redirect to /posts/1 via beforeLoad. Updates generated route tree/types to expose the new routes. Adds E2E tests for client/server redirects and a test id on the post view. Adjusts router core resolveRedirect to use buildLocation()'s Location object when setting redirect href and the Location header.

Changes

Cohort / File(s) Summary
Redirect feature routes
e2e/react-start/custom-basepath/src/routes/redirect/index.tsx, e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx, e2e/react-start/custom-basepath/src/routeTree.gen.ts
Adds /redirect index route with a link to /redirect/throw-it; adds /redirect/throw-it route whose beforeLoad throws a redirect to /posts/$postId (postId = "1"); updates generated route tree and type mappings to include these routes.
E2E tests
e2e/react-start/custom-basepath/tests/navigation.spec.ts
Adds tests for client-side redirect (clicking link) and direct-load server-side redirect to assert landing on the post view.
Post view test hook
e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx
Adds data-testid="post-view" to the root div to support E2E assertions.
Router core redirect resolution
packages/router-core/src/router.ts
In resolveRedirect, use buildLocation(redirect.options) once to obtain a Location object; set redirect.options.href = location.href while using the trimmed (origin-removed) location.url value for the Location header, preserving fallback behavior.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant B as Browser
  participant R as Router (match)
  participant Rt as /redirect/throw-it (beforeLoad)
  participant C as RouterCore.resolveRedirect
  participant P as /posts/1 (PostComponent)

  U->>B: Navigate to /base/redirect or /base/redirect/throw-it
  B->>R: Router matches route
  R->>Rt: call beforeLoad()
  Rt-->>R: throw redirect({ to: '/posts/$postId', params:{postId:'1'} })

  R->>C: resolveRedirect(redirect)
  note right of C: buildLocation(redirect.options) -> Location\nset redirect.options.href = location.href\nuse trimmed location.url for Location header
  C-->>R: normalized redirect info

  R->>B: instruct navigation to /base/posts/1
  B->>R: load /posts/1
  R->>P: render PostComponent
  P-->>U: displays post view (data-testid="post-view")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Sheraff

Poem

I hopped from /redirect with glee,
Tossed “throw-it!”—a swift decree.
The router built the proper way,
No doubled base, we bounce and stay.
Now at /posts/1 I nibble clover—tests pass green, the hop's all over. 🐇✨

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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly communicates the primary focus of the changes by highlighting the distinction between client-side and server-side redirect handling in the presence of rewrites, which directly corresponds to the modifications in RouterCore.resolveRedirect and the addition of matching end-to-end tests for both scenarios.
Linked Issues Check ✅ Passed The updates to RouterCore.resolveRedirect implement a unified location build that prevents duplicate basepath insertion, and the new end-to-end tests in the custom-basepath sample validate correct redirect behavior for both client-side and server-side flows, directly fulfilling the requirements of issue #5324.
Out of Scope Changes Check ✅ Passed All code modifications are confined to the redirect resolution logic in router-core and the supporting custom-basepath demonstration and tests, without introducing changes unrelated to fixing the basepath duplication issue and verifying its resolution.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-5324

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

Copy link

nx-cloud bot commented Oct 1, 2025

View your CI Pipeline Execution ↗ for commit 329e764

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

☁️ Nx Cloud last updated this comment at 2025-10-01 18:29:34 UTC

Copy link

pkg-pr-new bot commented Oct 1, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: 329e764

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
packages/router-core/src/router.ts (1)

2233-2238: Remove unused href computation and origin-stripping logic in resolveRedirect.

The local href variable (assigned from location.url) and the subsequent origin-stripping (lines 2234–2237) are never used—redirect.options.href is now set directly to location.href. Delete lines 2234–2237 to eliminate dead code and clarify the logic.

e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx (1)

5-8: Optional: avoid extra history entry on redirect.

Use replace: true so the throw route doesn’t pollute history.

     throw redirect({
       to: '/posts/$postId',
       params: { postId: '1' },
+      replace: true,
     })
e2e/react-start/custom-basepath/src/routes/redirect/index.tsx (1)

10-12: Minor: simplify markup and attach test id to Link.

Reduces nesting and keeps the interactive element the target.

-      <Link to="/redirect/throw-it">
-        <div data-testid="link-to-throw-it">Throw It</div>
-      </Link>
+      <Link to="/redirect/throw-it" data-testid="link-to-throw-it">
+        Throw It
+      </Link>
e2e/react-start/custom-basepath/tests/navigation.spec.ts (2)

49-54: Use Playwright's built-in visibility matcher for consistency.

The test logic is correct, but the assertion style differs from other tests in this file. Playwright provides toBeVisible() which is more idiomatic and consistent.

Apply this diff:

 test('client-side redirect', async ({ page }) => {
   await page.goto('/redirect')
   await page.getByTestId('link-to-throw-it').click()
 
-  expect(await page.getByTestId('post-view').isVisible()).toBe(true)
+  await expect(page.getByTestId('post-view')).toBeVisible()
 })

56-59: Use Playwright's built-in visibility matcher for consistency.

Same as the client-side test: prefer toBeVisible() for consistency with the rest of the test suite.

Apply this diff:

 test('server-side redirect', async ({ page }) => {
   await page.goto('/redirect/throw-it')
-  expect(await page.getByTestId('post-view').isVisible()).toBe(true)
+  await expect(page.getByTestId('post-view')).toBeVisible()
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af388a8 and fd1cce7.

📒 Files selected for processing (6)
  • e2e/react-start/custom-basepath/src/routeTree.gen.ts (13 hunks)
  • e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx (1 hunks)
  • e2e/react-start/custom-basepath/src/routes/redirect/index.tsx (1 hunks)
  • e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx (1 hunks)
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts (1 hunks)
  • packages/router-core/src/router.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:

  • packages/router-core/src/router.ts
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/src/routeTree.gen.ts
  • e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/index.tsx
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • packages/router-core/src/router.ts
e2e/**

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/src/routeTree.gen.ts
  • e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/index.tsx
**/src/routes/**

📄 CodeRabbit inference engine (AGENTS.md)

Place file-based routes under src/routes/ directories

Files:

  • e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/index.tsx
🧬 Code graph analysis (4)
packages/router-core/src/router.ts (1)
packages/router-core/src/redirect.ts (1)
  • redirect (57-93)
e2e/react-start/custom-basepath/src/routeTree.gen.ts (1)
e2e/react-router/js-only-file-based/src/routeTree.gen.js (1)
  • PostsIndexRoute (35-39)
e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx (2)
e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx (1)
  • Route (7-14)
e2e/react-start/custom-basepath/src/routes/redirect/index.tsx (1)
  • Route (3-5)
e2e/react-start/custom-basepath/src/routes/redirect/index.tsx (2)
e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx (1)
  • Route (7-14)
e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx (1)
  • Route (3-10)
⏰ 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 (6)
e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx (1)

3-10: LGTM: correct server/client redirect via beforeLoad.

This covers SSR (direct hit) and CSR (link click) as intended.

e2e/react-start/custom-basepath/src/routes/redirect/index.tsx (1)

3-5: Route registration looks good.

e2e/react-start/custom-basepath/src/routeTree.gen.ts (2)

18-21: Wiring is consistent for new redirect routes.

Imports, route updates, module augmentation for /redirect/throw-it, and root children all align.

Also applies to: 57-61, 72-76, 201-204, 271-277, 352-354


250-256: Trailing-slash inconsistency in generated /redirect/ index route

FileRoutesByPath['/redirect/'] currently has

path: '/redirect'
fullPath: '/redirect'

(without a trailing slash), whereas other index routes (e.g. /users/, /posts/) use

path: '/'
fullPath: '/users/'    // or '/posts/'

Please re-generate and confirm; if it still occurs, update the generator to emit

path: '/'
fullPath: '/redirect/'
e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx (1)

24-24: LGTM! Test ID enables redirect verification.

The addition of data-testid="post-view" correctly enables the new E2E tests to verify that redirects successfully render the post view component.

e2e/react-start/custom-basepath/tests/navigation.spec.ts (1)

49-59: Good test coverage for the basepath redirect fix.

These tests appropriately validate both client-side and server-side redirect scenarios under a custom basepath, directly addressing the fix for issue #5324. The successful rendering of post-view implicitly confirms that basepath duplication is not occurring.

Comment on lines +2234 to 2237
let href = location.url
if (this.origin && href.startsWith(this.origin)) {
href = href.replace(this.origin, '') || '/'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused origin-stripping code.

The local href variable (line 2234) is computed from location.url and then processed through origin-stripping logic (lines 2235-2237), but the result is never used. Line 2238 directly assigns location.href instead.

This dead code should be removed for clarity, or if the origin stripping is needed, the logic should be updated to use the processed href variable.

Apply this diff to remove the unused code:

   resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
     if (!redirect.options.href) {
       const location = this.buildLocation(redirect.options)
-      let href = location.url
-      if (this.origin && href.startsWith(this.origin)) {
-        href = href.replace(this.origin, '') || '/'
-      }
       redirect.options.href = location.href
       redirect.headers.set('Location', redirect.options.href)
     }
📝 Committable suggestion

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

Suggested change
let href = location.url
if (this.origin && href.startsWith(this.origin)) {
href = href.replace(this.origin, '') || '/'
}
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
if (!redirect.options.href) {
const location = this.buildLocation(redirect.options)
- let href = location.url
- if (this.origin && href.startsWith(this.origin)) {
- href = href.replace(this.origin, '') || '/'
- }
redirect.options.href = location.href
redirect.headers.set('Location', redirect.options.href)
}
🤖 Prompt for AI Agents
In packages/router-core/src/router.ts around lines 2234 to 2237, the local href
is computed from location.url and then origin-stripped but never used; remove
the dead origin-stripping logic (the let href = location.url and the subsequent
if that replaces this.origin) so the code uses location.href directly as
currently done, or if origin-stripping is required instead of removal, replace
the later assignment that uses location.href to use the processed href variable
and ensure '/' fallback remains.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd1cce7 and 329e764.

📒 Files selected for processing (6)
  • e2e/react-start/custom-basepath/src/routeTree.gen.ts (13 hunks)
  • e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx (1 hunks)
  • e2e/react-start/custom-basepath/src/routes/redirect/index.tsx (1 hunks)
  • e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx (1 hunks)
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts (1 hunks)
  • packages/router-core/src/router.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • e2e/react-start/custom-basepath/src/routes/posts.$postId.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • e2e/react-start/custom-basepath/tests/navigation.spec.ts
  • e2e/react-start/custom-basepath/src/routes/redirect/index.tsx
  • e2e/react-start/custom-basepath/src/routes/redirect/throw-it.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • e2e/react-start/custom-basepath/src/routeTree.gen.ts
  • packages/router-core/src/router.ts
e2e/**

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • e2e/react-start/custom-basepath/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/router.ts
🧬 Code graph analysis (2)
e2e/react-start/custom-basepath/src/routeTree.gen.ts (1)
e2e/react-router/js-only-file-based/src/routeTree.gen.js (1)
  • PostsIndexRoute (35-39)
packages/router-core/src/router.ts (1)
packages/router-core/src/redirect.ts (1)
  • redirect (57-93)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test

Comment on lines +58 to +61
id: '/redirect/',
path: '/redirect/',
getParentRoute: () => rootRouteImport,
} as any)
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Correct the RedirectIndexRoute path

Route.update currently sets path: '/redirect/', but the generated type metadata just below (see FileRoutesByPath['/redirect/']) expects /redirect without the trailing slash. That mismatch means the runtime route emits /redirect/ URLs while the type-safe APIs point to /redirect, which undermines the new redirect tests and can reintroduce the basepath duplication we’re fixing. Please align the runtime path with the metadata by dropping the trailing slash.

 const RedirectIndexRoute = RedirectIndexRouteImport.update({
   id: '/redirect/',
-  path: '/redirect/',
+  path: '/redirect',
   getParentRoute: () => rootRouteImport,
 } as any)
📝 Committable suggestion

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

Suggested change
id: '/redirect/',
path: '/redirect/',
getParentRoute: () => rootRouteImport,
} as any)
const RedirectIndexRoute = RedirectIndexRouteImport.update({
id: '/redirect/',
path: '/redirect',
getParentRoute: () => rootRouteImport,
} as any)
🤖 Prompt for AI Agents
In e2e/react-start/custom-basepath/src/routeTree.gen.ts around lines 58 to 61,
the RedirectIndexRoute is defined with path: '/redirect/' which includes a
trailing slash while the generated type metadata expects '/redirect' without it;
update the runtime route definition to use path: '/redirect' (drop the trailing
slash) so the emitted URLs match the type-safe FileRoutesByPath entries and
avoid basepath duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

dont ever again bother about the autogenerated file routeTree.gen.ts

Copy link
Contributor

Choose a reason for hiding this comment

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

@schiller-manuel Understood! I'll skip reviewing routeTree.gen.ts files in future reviews since they're autogenerated.


✏️ Learnings added
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.376Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: schiller-manuel
PR: TanStack/router#5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.559Z
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.

Comment on lines 2231 to 2245
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
if (!redirect.options.href) {
let href = this.buildLocation(redirect.options).url
const location = this.buildLocation(redirect.options)
let href = location.url
if (this.origin && href.startsWith(this.origin)) {
href = href.replace(this.origin, '') || '/'
}
redirect.options.href = href
redirect.headers.set('Location', redirect.options.href)
redirect.options.href = location.href
redirect.headers.set('Location', href)
}

if (!redirect.headers.get('Location')) {
redirect.headers.set('Location', redirect.options.href)
}

return redirect
}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 1, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use location.publicHref for the Location header and remove origin-stripping logic.

The past review comment correctly identified dead code at lines 2234-2237. The href variable is computed from location.url and then origin-stripped, but line 2238 assigns location.href (not the processed href) to redirect.options.href. Only the Location header uses the processed href.

Additionally, the manual origin-stripping is fragile—if location.url doesn't start with this.origin (e.g., due to misconfiguration or edge cases), the Location header would incorrectly contain the full URL with origin.

Since buildLocation returns publicHref (the rewritten public-facing path without origin, see lines 1689-1690), use that directly for the Location header.

Apply this diff:

 resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
   if (!redirect.options.href) {
     const location = this.buildLocation(redirect.options)
-    let href = location.url
-    if (this.origin && href.startsWith(this.origin)) {
-      href = href.replace(this.origin, '') || '/'
-    }
     redirect.options.href = location.href
-    redirect.headers.set('Location', href)
+    redirect.headers.set('Location', location.publicHref)
   }
   if (!redirect.headers.get('Location')) {
     redirect.headers.set('Location', redirect.options.href)
   }
   return redirect
 }

This ensures:

  • redirect.options.href contains the internal path (location.href)
  • The Location header contains the rewritten public path (location.publicHref), correctly handling basepath rewrites per the PR objective
📝 Committable suggestion

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

Suggested change
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
if (!redirect.options.href) {
let href = this.buildLocation(redirect.options).url
const location = this.buildLocation(redirect.options)
let href = location.url
if (this.origin && href.startsWith(this.origin)) {
href = href.replace(this.origin, '') || '/'
}
redirect.options.href = href
redirect.headers.set('Location', redirect.options.href)
redirect.options.href = location.href
redirect.headers.set('Location', href)
}
if (!redirect.headers.get('Location')) {
redirect.headers.set('Location', redirect.options.href)
}
return redirect
}
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
if (!redirect.options.href) {
const location = this.buildLocation(redirect.options)
redirect.options.href = location.href
redirect.headers.set('Location', location.publicHref)
}
if (!redirect.headers.get('Location')) {
redirect.headers.set('Location', redirect.options.href)
}
return redirect
}
🤖 Prompt for AI Agents
In packages/router-core/src/router.ts around lines 2231 to 2245, the code
computes and origin-strips an href from location.url and assigns location.href
to redirect.options.href while using the processed href for the Location header;
remove the origin-stripping and dead-code, keep redirect.options.href =
location.href, and set the Location header to location.publicHref (which already
contains the rewritten public-facing path) so headers use publicHref and
internal href stays as location.href.

Copy link
Contributor Author

@schiller-manuel schiller-manuel Oct 1, 2025

Choose a reason for hiding this comment

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

no, this is exactly what is wanted. in case the origin matches the router's origin (e.g. https://foo.com), then remove so we convert https://foo.com/bar to just /bar

only if the redirect points to a different origin, THEN we want a full url

Copy link
Contributor

Choose a reason for hiding this comment

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

@schiller-manuel, thank you for the clarification! I understand now—the origin-stripping logic is intentional to handle same-origin vs. cross-origin redirects correctly:

  • Same-origin redirects: Strip the origin to produce a relative path (e.g., https://foo.com/bar/bar)
  • Cross-origin redirects: Keep the full URL (e.g., https://other.com/bar remains unchanged)

This ensures the Location header contains the appropriate format based on whether the redirect target matches the router's origin. I appreciate you explaining the reasoning behind this implementation!


✏️ Learnings added
Learnt from: schiller-manuel
PR: TanStack/router#5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.559Z
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.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@schiller-manuel schiller-manuel merged commit a2da3f2 into main Oct 1, 2025
6 checks passed
@schiller-manuel schiller-manuel deleted the fix-5324 branch October 1, 2025 18:39
leesb971204 added a commit to leesb971204/router that referenced this pull request Oct 2, 2025
Signed-off-by: leesb971204 <leesb971204@gmail.com>
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.

Redirect with basepath duplicates base in URL causing notFoundError
1 participant