Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Jan 21, 2026

Summary by CodeRabbit

  • Refactor

    • Locations now expose href and publicHref plus an external flag; internal URL objects removed for a simpler navigation surface.
    • Link components across frameworks now use publicHref for display and router-created href for in-app navigation, standardizing link behavior.
  • Tests

    • Updated test assertions to rely on href/publicHref and external semantics.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

The PR replaces propagated URL objects with a string-based publicHref and an external: boolean on ParsedLocation, updates RouterCore to emit href/publicHref instead of url, and updates framework link components and tests to use publicHref and router.history.createHref for internal links.

Changes

Cohort / File(s) Summary
Core Location Shape & Routing
packages/router-core/src/router.ts, packages/router-core/src/location.ts
buildLocation/parse now produce href and publicHref and an external: boolean instead of propagating a URL object. Redirects, navigate/reload paths, history payloads, and helpers now use publicHref.
React Link
packages/react-router/src/link.tsx
useLinkProps now reads maskedLocation ?? next, uses location.publicHref/external; full-URL (external) short-circuits to { href: publicHref, external: true }, otherwise returns router.history.createHref(publicHref). Memo dependencies updated.
Solid Link
packages/solid-router/src/link.tsx
Same refactor: compute from next().maskedLocation ?? next(), return publicHref for external, otherwise router.history.createHref(publicHref).
Vue Link
packages/vue-router/src/link.tsx
useLinkProps switched to location?.publicHref and external checks; external returns publicHref, internal uses `router.history.createHref(publicHref)
Tests
packages/*/tests/* (React, Solid, Vue)
Tests updated to stop constructing URL objects from location.url; assertions now use location.href/publicHref. Redirect tests expect external: false and no url field in _fromLocation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant LinkComponent
  participant RouterCore
  participant RouterHistory
  participant BrowserHistory

  Client->>LinkComponent: render / click <to>
  LinkComponent->>RouterCore: call next() -> maskedLocation ?? next()
  RouterCore->>RouterCore: derive publicHref, href, external
  alt external === true
    LinkComponent->>Client: return { href: publicHref, external: true }
  else
    LinkComponent->>RouterHistory: router.history.createHref(publicHref)
    RouterHistory->>LinkComponent: created href (path)
    LinkComponent->>BrowserHistory: navigation (push/replace href)
    BrowserHistory->>RouterCore: commit location (payload uses publicHref)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 Hopped from heavy URL to a string so neat,
publicHref guides my tiny feet.
External flags wave from far-off lands,
Links now resolve by simpler hands.
A rabbit cheers with carrot treats and beats! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 accurately reflects the main refactoring objective: removing URL constructor calls in buildLocation and refactoring to use publicHref-based href derivation across the codebase.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@nx-cloud
Copy link

nx-cloud bot commented Jan 21, 2026

View your CI Pipeline Execution ↗ for commit 230a2ba

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

☁️ Nx Cloud last updated this comment at 2026-01-22 15:22:14 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 21, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 230a2ba

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

🤖 Fix all issues with AI agents
In `@packages/react-router/src/link.tsx`:
- Around line 131-149: The code currently treats full publicHref values as
internal by returning { href: publicHref, external: false }; update the handler
in link.tsx (the block that computes publicHref and isFullUrl) to mark full-URL
publicHref values as external (set external: true) so the Link component does
not intercept clicks for cross-origin rewrites; keep href as publicHref for
those cases and leave the router.history.createHref branch unchanged for
origin-stripped paths.

In `@packages/router-core/src/router.ts`:
- Around line 2646-2649: getParsedLocationHref currently returns an
origin-stripped location.href which loses cross-origin rewrite targets; change
getParsedLocationHref (used by resolveRedirect) to prefer the full URL (e.g.,
location.publicHref or location.url.href) and only strip the origin when it
exactly matches this.origin so same-origin behavior is preserved while
cross-origin redirects retain their full href; update any callers (notably
resolveRedirect) to use the new logic to emit full cross-origin URLs and keep
the existing same-origin stripping behavior.

In `@packages/solid-router/src/link.tsx`:
- Around line 140-158: The code treats full publicHref values as internal;
change the branch that detects full URLs (the isFullUrl check using
publicHref.startsWith('http://')/('https://')) to mark them external by
returning { href: publicHref, external: true } so SPA interception is disabled
for cross-origin rewrites; keep the other branch using
router.history.createHref(publicHref) for non-full paths.

In `@packages/vue-router/src/link.tsx`:
- Around line 474-493: The computed publicHref can be an absolute URL even for
an internal options.to, which risks client-side navigation attempting a
cross-origin push; update the href resolution and click handling so that when
publicHref is detected as a full URL (the logic in the block using
nextLocation?.maskedLocation ?? nextLocation and const publicHref =
location?.publicHref), you treat it as external: return the full publicHref and
signal/mark it as external (or short-circuit handleClick) so that handleClick
(the component's click handler that currently calls
router.navigate/router.history.createHref) does not attempt router.navigate or a
client-side push for that link; concretely, keep the isFullUrl detection, ensure
the resolver returns the absolute URL and modify handleClick to check the same
isFullUrl condition (or an external flag set alongside createHref) and fall back
to native navigation instead of calling router.navigate.

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

🤖 Fix all issues with AI agents
In `@packages/router-core/src/router.ts`:
- Around line 1885-1912: The internal href must be derived from fullPath before
running executeRewriteOutput to avoid in-place mutation of the URL changing our
internal path; change the block where this.rewrite is handled so href = fullPath
(or otherwise capture the origin-stripped path) before calling
executeRewriteOutput(this.rewrite, url), then compute publicHref from
rewrittenUrl (using rewrittenUrl.href when origins differ, or
rewrittenUrl.pathname+search+hash when same) so publicHref reflects the rewrite
while href remains the original internal path used by isSameUrl and redirects.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/router-core/src/router.ts (1)

2218-2226: Guard against cross‑origin rewrites during imperative navigation.
buildLocation now marks external when rewrite output changes origin. navigate({ to }) still proceeds with client-side commit and may hit a pushState cross-origin error. Consider forcing reloadDocument when location.external and using publicHref.

🛠️ Suggested fix
   navigate: NavigateFn = async ({
     to,
     reloadDocument,
     href,
     publicHref,
     ...rest
   }) => {
     let hrefIsUrl = false

     if (href) {
       try {
         new URL(`${href}`)
         hrefIsUrl = true
       } catch {}
     }

     if (hrefIsUrl && !reloadDocument) {
       reloadDocument = true
     }
+
+    let resolvedLocation: ParsedLocation | undefined
+    if (!reloadDocument && (to !== undefined || !href)) {
+      resolvedLocation = this.buildLocation({ to, ...rest } as any)
+      if (resolvedLocation.external) {
+        reloadDocument = true
+        href = href ?? resolvedLocation.publicHref
+        publicHref = publicHref ?? resolvedLocation.publicHref
+      }
+    }

     if (reloadDocument) {
       // When to is provided, always build a location to get the proper publicHref
       // (this handles redirects where href might be an internal path from resolveRedirect)
       // When only href is provided (no to), use it directly as it should already
       // be a complete path (possibly with basepath)
       if (to !== undefined || !href) {
-        const location = this.buildLocation({ to, ...rest } as any)
+        const location = resolvedLocation ?? this.buildLocation({ to, ...rest } as any)
         // Use publicHref which contains the path (origin-stripped is fine for reload)
         href = href ?? location.publicHref
         publicHref = publicHref ?? location.publicHref
       }
♻️ Duplicate comments (1)
packages/router-core/src/router.ts (1)

1886-1910: Keep internal href stable when rewrite output mutates the URL.
executeRewriteOutput can mutate url in-place; using url.href after rewrite can turn the internal href into the public path and break isSameUrl comparisons. Prefer the pre‑rewrite fullPath for href.

🛠️ Suggested fix
       if (this.rewrite) {
         // With rewrite, we need to construct URL to apply the rewrite
         const url = new URL(fullPath, this.origin)
         const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
-        href = url.href.replace(url.origin, '')
+        // Keep internal href stable even if rewrite.output mutates `url`
+        href = fullPath
         // If rewrite changed the origin, publicHref needs full URL
         // Otherwise just use the path components
         if (rewrittenUrl.origin !== this.origin) {
           publicHref = rewrittenUrl.href
           external = true

Also applies to: 1912-1921

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants