Which project does this relate to?
Start and Router
Describe the bug
When a parent route uses params.parse and params.stringify, a <Link reloadDocument> to a server-only child route can generate different hrefs during SSR and hydration.
The server applies the parent params.stringify, while the client does not.
Reproduction
Create a parent route that maps the public articles segment to the internal article value:
// routes/$postType/route.tsx
import { Outlet, createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/$postType')({
params: {
parse: ({ postType }) =>
postType === 'articles'
? { postType: 'article' as const }
: false,
stringify: ({ postType }) => ({
postType: postType === 'article' ? 'articles' : postType,
}),
},
component: Outlet,
})
Render a link from its index route:
// routes/$postType/index.tsx
import { Link, createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/$postType/')({
component: IndexComponent,
})
function IndexComponent() {
return (
<Link
to="/$postType/$postId/download"
params={{
postType: 'article',
postId: '1',
}}
reloadDocument
>
Download
</Link>
)
}
Define the destination as a server-only route:
// routes/$postType/$postId/download.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute(
'/$postType/$postId/download',
)({
server: {
handlers: {
GET: () => new Response('file'),
},
},
})
Then:
- Start the application.
- Visit
/articles.
- Check the hydration warning.
- Click the download link.
Actual behavior
SSR renders:
During hydration, the client generates:
React reports an href hydration mismatch.
The DOM may continue to show /articles/1/download, but clicking the Link uses the client-generated /article/1/download, resulting in a 404.
The server-only route is available in the generated route types, so the Link passes TypeScript validation. However, it is absent from the client runtime route tree, so client-side URL generation does not apply the parent params.stringify.
This was discovered in the same application as #7731, but appears to be a separate issue involving server-only route pruning.
Expected behavior
SSR and client-side URL generation should produce the same canonical href:
A type-safe Link to a server-only route should apply the same parent params.stringify chain on both sides.
If linking to server-only routes is intentionally unsupported, the generated route types should prevent this usage or the limitation should be documented.
Current workaround
Adding an empty component keeps the destination in the client route tree:
export const Route = createFileRoute(
'/$postType/$postId/download',
)({
component: () => null,
server: {
handlers: {
GET: () => new Response('file'),
},
},
})
After adding the component, both SSR and the client generate /articles/1/download.
Version
@tanstack/react-router: 1.170.18
@tanstack/react-start: 1.168.32
Which project does this relate to?
Start and Router
Describe the bug
When a parent route uses
params.parseandparams.stringify, a<Link reloadDocument>to a server-only child route can generate different hrefs during SSR and hydration.The server applies the parent
params.stringify, while the client does not.Reproduction
Create a parent route that maps the public
articlessegment to the internalarticlevalue:Render a link from its index route:
Define the destination as a server-only route:
Then:
/articles.Actual behavior
SSR renders:
During hydration, the client generates:
React reports an href hydration mismatch.
The DOM may continue to show
/articles/1/download, but clicking the Link uses the client-generated/article/1/download, resulting in a 404.The server-only route is available in the generated route types, so the Link passes TypeScript validation. However, it is absent from the client runtime route tree, so client-side URL generation does not apply the parent
params.stringify.This was discovered in the same application as #7731, but appears to be a separate issue involving server-only route pruning.
Expected behavior
SSR and client-side URL generation should produce the same canonical href:
A type-safe Link to a server-only route should apply the same parent
params.stringifychain on both sides.If linking to server-only routes is intentionally unsupported, the generated route types should prevent this usage or the limitation should be documented.
Current workaround
Adding an empty component keeps the destination in the client route tree:
After adding the component, both SSR and the client generate
/articles/1/download.Version