-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Which project does this relate to?
Start
Describe the bug
Describe the bug
When defining links in the head option of both a parent route and a child route, the links from all matched routes are simply concatenated without any deduplication logic. This means a child route cannot override a parent route's link — for example, <link rel="canonical"> defined in the root route cannot be overridden by a child route's canonical link. Both end up in the rendered HTML, which is invalid and harmful for SEO.
This is inconsistent with how meta tags are handled. Meta tags are correctly deduplicated by name or property attribute, with child routes taking priority over parent routes. Links have no such mechanism.
Your Example Website or App
Minimal reproduction:
// __root.tsx
export const Route = createRootRoute({
head: () => ({
links: [
{ rel: 'canonical', href: 'https://example.com/' },
],
}),
// ...
})
// blog/index.tsx
export const Route = createFileRoute('/blog/')({
head: () => ({
links: [
{ rel: 'canonical', href: 'https://example.com/blog' },
],
}),
// ...
})When visiting /blog, the rendered HTML contains both canonical links:
<link rel="canonical" href="https://example.com/" />
<link rel="canonical" href="https://example.com/blog" />Expected: only https://example.com/blog should be present.
Steps to Reproduce
- Define a
<link rel="canonical">in the root route'shead.links - Define a different
<link rel="canonical">in a child route'shead.links - Navigate to the child route
- Inspect the rendered
<head>— both canonical links are present
Expected behavior
Child route's links with the same rel attribute (at least for unique-by-nature rels like canonical) should override the parent route's, similar to how meta tags are deduplicated by name/property.
Source Code Analysis
In headContentUtils.js:
- Meta tags (lines 17–49): iterated from last match (deepest child) to first (root), deduplicated by
name/property— child wins. - Links (lines 75–83): simply
state.matches.map(match => match.links).flat(1)— no dedup byrelor any other attribute.
The final uniqBy (line 140) deduplicates by JSON.stringify, but since two canonical links have different href values, they are not considered duplicates.
How often does this bug happen?
Every time
Platform
- @tanstack/react-router: v1.132.0
- OS: macOS
- Browser: All
Your Example Website or App
/
Steps to Reproduce the Bug or Issue
/
Expected behavior
/
Screenshots or Videos
No response
Platform
- @tanstack/react-router: v1.161.3
- OS: macOS
- Browser: All
Additional context
No response