Inline scripts are duplicated after hydration when the CSP nonce is sent in a header
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Newbie friendliness
- 90/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- react, typescript
- Domain
- frontend
Research direction
Start in packages/react-router/src/Asset.tsx around the inline script lookup at line 204. Check how the existing script's nonce is read during hydration, then verify the behavior with the linked minimal reproducer or a Start app using a CSP nonce in a response header. Done means the server-rendered script is matched and no duplicate script is appended.
Written by the indexing model from the issue text.
Description
Which project does this relate to?
Router
Describe the bug
After hydration, Script looks for an existing inline script with the same text, type and nonce before it creates a new one:
When the CSP arrives in a response header, browsers apply nonce hiding. On a connected element, getAttribute('nonce') returns '', and only the .nonce property keeps the value. So the comparison is '' vs. the real nonce, and the server-rendered script never matches. Script then creates a new <script type="module" nonce>import("/assets/index-….js")</script>, sets its textContent, and appends it to <head>.
Complete minimal reproducer
https://github.com/gabriel-le/tanstack-nonce-inline-scripts-duplication
Steps to Reproduce the Bug
This works in any Start app:
- Set
ssr.nonceon the router and sendContent-Security-Policy: script-src 'nonce-<n>'as a response header. - Load a page.
document.head.querySelectorAll('script[type=module]')includes a re-addedimport("/assets/…")script. ItsgetAttribute('nonce')is'', and its.noncehas the value. - Control: send the same policy in a
<meta http-equiv>, or send no policy. Nonces aren't hidden, the lookup matches, and nothing is added. - Add
; require-trusted-types-for 'script'to the header. Hydration fails with the error above.
I tested this using react-router, but the issue is likely the same in solid and vue
Expected behavior
The lookup matches the server-rendered script, and nothing is created or appended.
Proposed fix
- const sNonce = el.getAttribute('nonce') ?? undefined
+ const sNonce = el.nonce || undefined
The fix uses || rather than ?? because .nonce is '' when an element has no nonce. With ??, apps that don't use a nonce would start creating duplicates.
Screenshots or Videos
No response
Platform
- Router / Start Version: 1.170.40
- OS: macOS
- Browser: Tested in chrome, safari and firefox
- Browser Version: Safari Version 27.2 (22625.2.5.11.1)
- Bundler: vite
- Bundler Version: 8.0.0
Additional context
No response
- Dominant language
- TypeScript
- Stars
- 15.1k
- Forks
- 1.9k
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 135
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing 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.
More from TanStack/router
-
start-plugin-core: sitemap lastmod and pages.json lastBuilt are stamped with the build's wall clock (no SOURCE_DATE_EPOCH support)Possibly taken @treadnot claimed this 7 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
Empty .ts route file is scaffolded with JSX, breaking all route generationPossibly taken @nathan-logan claimed this 11 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
information needed
Difficulty 1/5 Under an hour Newbie friendliness 90/100
TanStack/router#8464 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
TanStack/router#8407 · 1 reaction ·
Maintainers usually reply within 1 day
-
Start: untagged 2xx non-JSON server-function response resolves as a raw `Response` instead of rejectingPossibly taken @mixelburg claimed this 19 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
TanStack/router#8333 · 1 reaction ·
Maintainers usually reply within 1 day
Similar issues
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 88/100
lingdojo/kana-dojo#31665 · 1 comment · 5 reactions ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
CopilotKit/CopilotKit#7618 · 1 comment ·
Maintainers usually reply within 1 day
-
bug ready-for-agent
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
sleeyax/paseo-plugins#112 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 4 days