`head`: child route `links` cannot override parent route links (e.g. `rel="canonical"`)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.1k
- Forks
- 1.9k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 143
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in headContentUtils.js, especially the existing meta-tag deduplication and the links aggregation around lines 75–83 and final uniqBy around line 140. Reproduce the parent and child canonical-link example, then make the completed behavior ensure the child canonical link replaces the parent without introducing duplicate canonical links.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100