Which project does this relate to?
Router
Describe the bug
A script declared in a route's head() with src (analytics, tag manager, chat widget, pixel) is removed from <head> and re-executed on every client-side navigation that changes head tags. Analytics count every internal click as a fresh visit; widgets boot over and over.
Live repro on @tanstack/react-router 1.170.32 (Playwright, real Chromium): a root head() with scripts: [{ src: '/track.js' }] where track.js increments window.__trackCount:
AFTER SSR LOAD /: {"count":1}
AFTER NAV -> /pool/123: {"count":2}
AFTER NAV -> /: {"count":3}
count should stay 1. Full repro app + script: https://github.com/breken-ai/tanstack-router-headscripts-repro
Root cause
In packages/react-router/src/Asset.tsx, the Script component's injection useEffect keys off [attrs, children, dataScript]. attrs is a freshly spread object on every head-tag computation, so any navigation that rebuilds head tags re-fires the effect: the cleanup removes the injected <script src> node and the effect appends a new one, which the browser executes again.
Compounding it: after the hydration flip the component renders null, so React removes the SSR-adopted <script> node - the dedup loop (querySelectorAll('script[src]')) can never find the SSR copy, so even the first revisit re-executes. Verified live: right after SSR load, document.head already contains zero /track.js nodes.
Your Example Website or App
https://github.com/breken-ai/tanstack-router-headscripts-repro
Steps to Reproduce the Bug or Issue
npm install && npx vite dev --port 3000
node verify-headscripts.mjs (or click between index and /pool/123 by hand and watch window.__trackCount)
- The script executes once per navigation instead of once ever.
Expected behavior
A head() script with src is injected and executed exactly once while its route stays matched, no matter how often head tags are recomputed.
Screenshots or Videos
No response
Platform
- OS: any (reproduced on Linux, Chromium)
- Browser: any
@tanstack/react-router 1.170.32 (latest) and current main
Additional context
Fix PR incoming: the effect should key off a stable serialization of the script's attributes instead of object identity.
Built by breken, your AI support engineer - breken.ai - this one's on us.
Which project does this relate to?
Router
Describe the bug
A script declared in a route's
head()withsrc(analytics, tag manager, chat widget, pixel) is removed from<head>and re-executed on every client-side navigation that changes head tags. Analytics count every internal click as a fresh visit; widgets boot over and over.Live repro on
@tanstack/react-router1.170.32 (Playwright, real Chromium): a roothead()withscripts: [{ src: '/track.js' }]wheretrack.jsincrementswindow.__trackCount:countshould stay 1. Full repro app + script: https://github.com/breken-ai/tanstack-router-headscripts-reproRoot cause
In
packages/react-router/src/Asset.tsx, theScriptcomponent's injectionuseEffectkeys off[attrs, children, dataScript].attrsis a freshly spread object on every head-tag computation, so any navigation that rebuilds head tags re-fires the effect: the cleanup removes the injected<script src>node and the effect appends a new one, which the browser executes again.Compounding it: after the hydration flip the component renders
null, so React removes the SSR-adopted<script>node - the dedup loop (querySelectorAll('script[src]')) can never find the SSR copy, so even the first revisit re-executes. Verified live: right after SSR load,document.headalready contains zero/track.jsnodes.Your Example Website or App
https://github.com/breken-ai/tanstack-router-headscripts-repro
Steps to Reproduce the Bug or Issue
npm install && npx vite dev --port 3000node verify-headscripts.mjs(or click betweenindexand/pool/123by hand and watchwindow.__trackCount)Expected behavior
A
head()script withsrcis injected and executed exactly once while its route stays matched, no matter how often head tags are recomputed.Screenshots or Videos
No response
Platform
@tanstack/react-router1.170.32 (latest) and currentmainAdditional context
Fix PR incoming: the effect should key off a stable serialization of the script's attributes instead of object identity.