Skip to content

Conversation

leesb971204
Copy link
Contributor

@leesb971204 leesb971204 commented Sep 24, 2025

fixes #5200

Problem

When using the new rewrite system with basepath, redirects from / to /basepath were not working in client-side navigation.

Root Cause

The buildLocation function was returning the original fullPath for the href property instead of applying the output rewrite. This caused the Transitioner component to compare non-rewritten URLs, preventing proper redirect detection.

Summary by CodeRabbit

  • Bug Fixes

    • Links/href generation now uses the rewritten path (without origin) to prevent mixed-origin or malformed links while keeping the full rewritten URL for navigation and deep links.
  • Documentation

    • Examples updated to demonstrate the new rewrite API shape and composing rewrites; public interfaces/signatures unchanged.
  • Tests

    • Added tests validating basepath rewrite behavior and backward compatibility with basepath option.

…ation

Signed-off-by: leesb971204 <leesb971204@gmail.com>
Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

parseLocation now sources internal href from the rewritten URL (origin removed) and sets url to the full rewritten href; publicHref remains pathname+search+hash. Documentation examples updated to use rewriteBasepath({ basepath }) and composeRewrites. Two new tests validate basepath rewrite and backward-compat behavior.

Changes

Cohort / File(s) Summary
Router href & docs update
packages/router-core/src/router.ts
parseLocation now derives href from rewrittenUrl.href.replace(rewrittenUrl.origin, '') instead of fullPath; url uses rewrittenUrl.href; publicHref remains rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash. Examples/docs switch to rewriteBasepath({ basepath: '/basepath' }) and composeRewrites([...]). No public API signature changes.
Tests: basepath / rewrite behavior
packages/react-router/tests/router.test.tsx
Added two tests verifying: (1) rewriteBasepath serves app root while browser URL retains basepath, and (2) backward-compatible basepath option produces same behavior. Tests assert rendered route and sync between router state and history pathname.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Router
  participant Rewriter

  Client->>Router: parseLocation(rawLocation)
  Router->>Rewriter: apply rewrite rules(rawLocation)
  Rewriter-->>Router: rewrittenUrl { href, origin, pathname, search, hash }
  Router->>Router: href = rewrittenUrl.href.replace(rewrittenUrl.origin, '')
  Router->>Router: url = rewrittenUrl.href
  Router->>Router: publicHref = rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash
  Router-->>Client: Location { href, url, publicHref, ... }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Insik-Han

Poem

A rabbit hops through rewritten ways,
I trim the origin, brighten the blaze.
href now neat, url keeps the whole,
publicHref shows the visible stroll.
🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely describes the primary fix of applying the rewrite logic to the href property in buildLocation, matching the code changes without extraneous details.
Linked Issues Check ✅ Passed The changes implement the output rewrite of the href property in buildLocation to ensure basepath behavior and include new unit tests that verify redirects from “/” to the basepath, directly addressing the objectives of issue #5200.
Out of Scope Changes Check ✅ Passed All modifications are confined to applying the rewrite logic in router-core and adding related tests for basepath handling, and there are no unrelated or extraneous code changes present.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

nx-cloud bot commented Sep 24, 2025

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit b9a4ce3

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

☁️ Nx Cloud last updated this comment at 2025-09-29 00:33:52 UTC

Copy link

pkg-pr-new bot commented Sep 24, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: b9a4ce3

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 (3)
packages/router-core/src/router.ts (3)

1683-1690: Minor: Build href from URL parts instead of stripping origin

Safer and clearer to concatenate pathname + search + hash than using replace on href (avoids accidental substring removal if origin appears elsewhere).

-        href: rewrittenUrl.href.replace(rewrittenUrl.origin, ''),
+        href: rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,

1781-1783: Align equality check with new href semantics (compare publicHref)

buildLocation.href is now output‑rewritten while latestLocation.href (from parse) remains input‑rewritten. Comparing them will false‑negative under basepath. Compare publicHref on both sides to retain the “no-op if nothing changed” optimization.

-    const isSameUrl =
-      trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
+    const isSameUrl =
+      trimPathRight(this.latestLocation.publicHref) ===
+      trimPathRight(next.publicHref)

1939-1945: SSR parity: compare and redirect using publicHref

Same reasoning as above: avoid comparing input‑rewritten href to output‑rewritten href. Use publicHref for both the comparison and redirect target to prevent unnecessary redirects (or loops) when a basepath rewrite is active.

-      if (
-        trimPath(normalizeUrl(this.latestLocation.href)) !==
-        trimPath(normalizeUrl(nextLocation.href))
-      ) {
-        throw redirect({ href: nextLocation.href })
-      }
+      if (
+        trimPath(normalizeUrl(this.latestLocation.publicHref)) !==
+        trimPath(normalizeUrl(nextLocation.publicHref))
+      ) {
+        throw redirect({ href: nextLocation.publicHref })
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 065722a and 23be5f9.

📒 Files selected for processing (1)
  • packages/router-core/src/router.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{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
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
⏰ 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

@schiller-manuel
Copy link
Contributor

schiller-manuel commented Sep 26, 2025

thanks for the PR!
any idea why the e2e tests fail?

also, we should add tests for this (ideally some unit tests)

@leesb971204
Copy link
Contributor Author

thanks for the PR! any idea why the e2e tests fail?

also, we should add tests for this (ideally some unit tests)

I’m not really sure — honestly, I’m not very familiar with TanStack Start.
When I run the Start example on my branch, the app gets stuck due to excessive redirects caused by the basepath.
Do you have any idea what might be happening?

I’ll go ahead and add a unit test in the meantime!

…ot route

Signed-off-by: leesb971204 <leesb971204@gmail.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 (2)
packages/react-router/tests/router.test.tsx (2)

2701-2732: Good coverage of basepath on root; add a test for “/” → basepath redirect

This validates internal “/” vs browser “/my-app/” when landing on “/my-app/”. To guard against regressions for the original bug (missing redirect from “/” to basepath), add a companion test starting at “/” and asserting history ends at “/my-app/” while router stays at “/”.

Apply this diff near this test:

+  it('should redirect from "/" to basepath and keep internal pathname "/"', async () => {
+    const rootRoute = createRootRoute({ component: () => <Outlet /> })
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => <div data-testid="home">Home</div>,
+    })
+    const routeTree = rootRoute.addChildren([indexRoute])
+
+    const history = createMemoryHistory({ initialEntries: ['/'] })
+    const router = createRouter({
+      routeTree,
+      history,
+      rewrite: rewriteBasepath({ basepath: '/my-app' }),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    await waitFor(() => {
+      expect(screen.getByTestId('home')).toBeInTheDocument()
+    })
+
+    expect(router.state.location.pathname).toBe('/')
+    // Allow either trailing or non-trailing slash if normalization changes:
+    expect(['/my-app', '/my-app/']).toContain(history.location.pathname)
+  })

2734-2765: Back-compat test looks good; also assert navigation applies output rewrite with basepath option

This confirms basepath option parity on initial load. Add a navigation test mirroring the Link-based case to ensure output rewrite occurs with basepath option too.

Apply this diff near this block:

+  it('basepath option: Link navigation applies output rewrite', async () => {
+    const rootRoute = createRootRoute({ component: () => <Outlet /> })
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => (
+        <div>
+          <Link to="/about" data-testid="about-link">About</Link>
+        </div>
+      ),
+    })
+    const aboutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/about',
+      component: () => <div data-testid="about">About</div>,
+    })
+    const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])
+
+    const history = createMemoryHistory({ initialEntries: ['/my-app/'] })
+    const router = createRouter({ routeTree, history, basepath: '/my-app' })
+
+    render(<RouterProvider router={router} />)
+    const aboutLink = await screen.findByTestId('about-link')
+    fireEvent.click(aboutLink)
+
+    await waitFor(() => {
+      expect(screen.getByTestId('about')).toBeInTheDocument()
+    })
+    expect(router.state.location.pathname).toBe('/about')
+    expect(history.location.pathname).toBe('/my-app/about')
+  })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 593cb78 and a9dbeb7.

📒 Files selected for processing (2)
  • packages/react-router/tests/router.test.tsx (1 hunks)
  • packages/router-core/src/router.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/router-core/src/router.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • packages/react-router/tests/router.test.tsx
packages/{react-router,solid-router}/**

📄 CodeRabbit inference engine (AGENTS.md)

Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Files:

  • packages/react-router/tests/router.test.tsx
🧬 Code graph analysis (1)
packages/react-router/tests/router.test.tsx (5)
packages/react-router/src/route.tsx (2)
  • createRootRoute (566-618)
  • createRoute (310-390)
packages/react-router/src/Match.tsx (1)
  • Outlet (309-359)
packages/history/src/index.ts (1)
  • createMemoryHistory (568-614)
packages/react-router/src/router.ts (1)
  • createRouter (80-82)
packages/react-router/src/RouterProvider.tsx (1)
  • RouterProvider (47-56)
⏰ 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

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.

v1.132.2 basePath is not working for browser url
2 participants