TanStack / TanStack/router

[Start] Bare basepath URL (e.g. /preview) redirects to /preview/ on SSR even with trailingSlash: 'never'

Open
#7,291 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Discussed in https://github.com/TanStack/router/discussions/7282

Originally posted by Nandan1996 April 28, 2026

Summary

When basepath is set on the router (e.g. /preview) and a user visits the bare basepath URL (http://localhost:3000/preview), the SSR handler issues a redirect to http://localhost:3000/preview/ (with trailing slash). This happens even though trailingSlash defaults to "never".

Visiting / with no basepath does not redirect to /-with-anything, so the behavior is asymmetric.

Where it happens

In packages/router-core/src/router.ts beforeLoad, on the server, the router rebuilds the location and compares publicHref:

if (isServer ?? this.isServer) {
  const nextLocation = this.buildLocation({
    to: this.latestLocation.pathname,
    search: true, params: true, hash: true, state: true,
    _includeValidateSearch: true,
  })

  if (this.latestLocation.publicHref !== nextLocation.publicHref) {
    const href = this.getParsedLocationHref(nextLocation)
    if (nextLocation.external) throw redirect({ href })
    else throw redirect({ href, _builtLocation: nextLocation })
  }
}

Walkthrough for request GET /preview with basepath: "/preview":

  1. Basepath is stripped → internal latestLocation.pathname = /.
  2. buildLocation({ to: "/" }) re-joins basepath + / → produces /preview/.
  3. Incoming publicHref = /preview, rebuilt publicHref = /preview/ → mismatch → 308 redirect.

Root case (no basepath): both incoming and rebuilt are /, so no redirect. That is why root "just works" but a bare basepath does not.

Expected

With trailingSlash: "never" (the default), GET /preview should be served as the canonical URL — no redirect to /preview/.

Repro
  • TanStack Start app
  • Set basepath: "/preview" in the router config (and matching dev server base/routerBasepath)
  • Visit http://localhost:3000/preview → 308 → http://localhost:3000/preview/
Related
  • #4105 — similar shape (visiting / redirects to /basepath/), but the bare-basepath case here looks distinct.
Question

Is this intentional canonicalization (treat bare basepath as non-canonical), or a bug where trailingSlash is not applied to the rebuilt publicHref before the equality check?

Versions: @tanstack/react-router@1.167.5, @tanstack/react-start@1.166.18.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/router-core/src/router.ts at the server-side beforeLoad logic that rebuilds the location and compares publicHref. Reproduce GET /preview with basepath "/preview" and trailingSlash set to "never", then verify the canonical bare-basepath URL is served without redirecting to /preview/.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
full-stack, web-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.