Which project does this relate to?
Router
Describe the bug
A splat param ($ / _splat) spans multiple path segments, so the only way to consume it is to split it on /. But the router decodes the whole splat with decodeURIComponent before handing it over, which turns any %2F inside a segment into a literal /. By the time application code splits, the separator and the encoded slash are the same character and the original segmentation is unrecoverable.
Notably, the router is already careful about this elsewhere: location.pathname is decoded with decodeURI (via decodePath), which by design leaves reserved characters such as %2F encoded. So for the same URL, location.pathname preserves the distinction and params._splat destroys it.
Reproduction
StackBlitz:
import {
createRootRoute, createRoute, createRouter, createMemoryHistory,
} from "@tanstack/react-router";
const root = createRootRoute();
const named = createRoute({ getParentRoute: () => root, path: "/make/$make" });
const splat = createRoute({ getParentRoute: () => root, path: "/parts/$" });
const routeTree = root.addChildren([named, splat]);
async function inspect(url) {
const router = createRouter({
routeTree,
history: createMemoryHistory({ initialEntries: [url] }),
});
await router.load();
const match = router.state.matches.at(-1);
console.log(url);
console.log(" params :", JSON.stringify(match.params));
console.log(" location.pathname:", JSON.stringify(router.state.location.pathname));
if (match.params._splat !== undefined) {
console.log(" _splat.split('/'):", JSON.stringify(match.params._splat.split("/")));
}
}
// "Ford/New Holland" is one value containing a slash, encoded as %2F.
await inspect("/make/Ford%2FNew%20Holland");
await inspect("/parts/make/Ford%2FNew%20Holland");
// For comparison, a genuinely two-segment path.
await inspect("/parts/make/Ford/New%20Holland");
Output:
/make/Ford%2FNew%20Holland
params : {"make":"Ford/New Holland"}
location.pathname: "/make/Ford%2FNew Holland"
/parts/make/Ford%2FNew%20Holland
params : {"*":"make/Ford/New Holland","_splat":"make/Ford/New Holland"}
location.pathname: "/parts/make/Ford%2FNew Holland"
_splat.split('/'): ["make","Ford","New Holland"]
/parts/make/Ford/New%20Holland
params : {"*":"make/Ford/New Holland","_splat":"make/Ford/New Holland"}
location.pathname: "/parts/make/Ford/New Holland"
_splat.split('/'): ["make","Ford","New Holland"]
The last two URLs are different — one has an encoded slash inside a segment, the other has a real separator — and location.pathname still tells them apart. But they produce an identical _splat, so any consumer that splits on / cannot distinguish them.
Complete minimal reproducer
https://stackblitz.com/github/rshaul/tanstack-repro
Steps to Reproduce the Bug
- Go to https://stackblitz.com/github/rshaul/tanstack-repro
- Inspect console
Expected behavior
_splat should preserve the segmentation of the matched path, so that splitting it on / reproduces the segments the URL actually had
Screenshots or Videos
No response
Platform
@tanstack/react-router 1.170.18
@tanstack/router-core 1.171.15
- Node 24.15.0
Additional context
No response
Which project does this relate to?
Router
Describe the bug
A splat param (
$/_splat) spans multiple path segments, so the only way to consume it is to split it on/. But the router decodes the whole splat withdecodeURIComponentbefore handing it over, which turns any%2Finside a segment into a literal/. By the time application code splits, the separator and the encoded slash are the same character and the original segmentation is unrecoverable.Notably, the router is already careful about this elsewhere:
location.pathnameis decoded withdecodeURI(viadecodePath), which by design leaves reserved characters such as%2Fencoded. So for the same URL,location.pathnamepreserves the distinction andparams._splatdestroys it.Reproduction
StackBlitz:
Output:
The last two URLs are different — one has an encoded slash inside a segment, the other has a real separator — and
location.pathnamestill tells them apart. But they produce an identical_splat, so any consumer that splits on/cannot distinguish them.Complete minimal reproducer
https://stackblitz.com/github/rshaul/tanstack-repro
Steps to Reproduce the Bug
Expected behavior
_splatshould preserve the segmentation of the matched path, so that splitting it on/reproduces the segments the URL actually hadScreenshots or Videos
No response
Platform
@tanstack/react-router1.170.18@tanstack/router-core1.171.15Additional context
No response