Skip to content

Path params starting with $ are rewritten to "undefined" on page load #7598

@DreierF

Description

@DreierF

Which project does this relate to?

Router

Describe the bug

A path param value starting with $ is destroyed on full page load: the router rewrites the URL to /files/undefined via history.replaceState and the param value becomes the string "undefined".

With pathParamsAllowedCharacters: ['$'], the router itself generates such URLs: a <Link to="/files/$filePath" params={{ filePath: '$EXAMPLE_CODE/file.abap' }}> navigates to /files/$EXAMPLE_CODE%2Ffile.abap and works fine client-side, but reloading that URL mangles it.

Claude's root cause analysis: on mount, <Transitioner> round-trips the current location through buildLocation({ to: router.latestLocation.pathname, ... }). buildLocation treats to as a route template, so interpolatePath interprets the concrete segment $EXAMPLE_CODE%2Ffile.abap as a param placeholder named EXAMPLE_CODE%2Ffile.abap.
No such param exists, so it interpolates "undefined" (encodeParam(key, params, decoder) ?? "undefined" in router-core's path.ts). The rebuilt publicHref differs from the current one, so the broken location is committed with replace: true. A second symptom of the same ambiguity: the rebuilt to containing $ doesn't exactly match any route template, so buildLocation sets destRoutes = [] and skips all search middlewares for that navigation.

Complete minimal reproducer

https://stackblitz.com/edit/github-arh22bkl?file=src%2Fmain.tsx

Steps to Reproduce the Bug

  1. npm install && npm run dev
  2. Open http://localhost:5173/ and click the link — the URL becomes /files/$EXAMPLE_CODE%2Ffile.abap and the view renders the param correctly
  3. Reload the page
  4. The URL is replaced with /files/undefined and useParams() returns filePath: "undefined"

Expected behavior

As a user, I expected a page reload of a router-generated URL to render the same route with the same param value, but the URL is rewritten to /files/undefined and the param value is lost.

Screenshots or Videos

No response

Platform

  • Router / Start Version: 1.169.2
  • OS: macOS 26.5.1
  • Browser: Chrome
  • Browser Version: 149
  • Bundler: vite
  • Bundler Version: 8.0.10 (reproduction uses vite 7 as I ran into some napi crashes in Stackblitz)

Additional context

Workaround: percent-encode $ as %24 in the pathname (e.g. via history.replaceState) before the router parses the location.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions