Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Oct 17, 2025

fixes #5511

Summary by CodeRabbit

  • New Features
    • Improved Content Security Policy (CSP) support: nonces are now applied consistently to head tags, scripts, and styles.
    • Automatic emission of a CSP nonce meta tag when a nonce is present.
    • SSR hydration now reads and applies the CSP nonce so server-rendered pages and client hydration share the same nonce.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Removes explicit nonce prop from React Asset script handling and adds per-request CSP nonce extraction and propagation: HeadContent and Scripts in both React and Solid routers now derive nonce from router.options.ssr, attach it to generated head/link/style/script attrs, and ssr-client reads nonce from a meta[csp-nonce] during hydration.

Changes

Cohort / File(s) Summary
React Router — Asset script change
packages/react-router/src/Asset.tsx
Removed passing/acceptance of nonce for Script; script elements no longer receive nonce via Script prop.
React Router — Head tag nonce propagation
packages/react-router/src/HeadContent.tsx
Extracts nonce from router.options.ssr, includes nonce on generated tag attrs (meta/link/style/script), and conditionally emits a meta[property="csp-nonce"]. Passes nonce into Asset rendering flow.
React Router — Scripts augmented with nonce
packages/react-router/src/Scripts.tsx
Reads nonce from router.options.ssr and merges it into asset/script attrs during script construction; Asset invocation no longer uses an explicit nonce prop.
Router Core — SSR client nonce extraction
packages/router-core/src/ssr/ssr-client.ts
On hydration, queries meta[property="csp-nonce"] and assigns its content to router.options.ssr.nonce.
Solid Router — Head tag nonce propagation
packages/solid-router/src/HeadContent.tsx
Extracts nonce from router.options.ssr, spreads nonce into generated meta/link/style/script attrs, and conditionally emits meta[property="csp-nonce"].
Solid Router — Scripts augmented with nonce
packages/solid-router/src/Scripts.tsx
Reads nonce from router.options.ssr and includes it in both asset script attrs and inline script attrs during rendering.

Sequence Diagram(s)

sequenceDiagram
    participant Server as SSR Server
    participant Head as HeadContent (React/Solid)
    participant Scripts as Scripts (React/Solid)
    participant Asset as Asset Renderer
    participant Browser as Browser DOM
    participant Client as ssr-client.ts

    Server->>Head: render HTML (router.options.ssr may include nonce)
    Head->>Head: const nonce = router.options.ssr?.nonce
    Head->>Head: include meta[property="csp-nonce"] if nonce
    Head->>Asset: render head assets with attrs + nonce
    Head->>Scripts: render scripts with attrs + nonce
    Scripts->>Asset: render script assets (attrs include nonce)
    Browser->>Client: hydration begins
    Client->>Browser: query meta[property="csp-nonce"]
    Browser-->>Client: return content (nonce)
    Client->>Client: set router.options.ssr = { nonce }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Insik-Han

Poem

🐰
From burrow to branch the nonce I bore,
Threaded each head tag, then dashed to the door.
Meta and script now wear my small key,
Safe hops and bright pages — a secure jubilee! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "fix: nonce" is extremely vague and generic, using minimal descriptive terminology that fails to convey meaningful information about the changeset. While the title is technically related to the changes (the PR does address a nonce-related issue), it provides no context about what specific problem is being fixed, such as the fact that nonce attributes were not being applied to all inline scripts in head elements. A teammate scanning the commit history would not understand the scope or purpose of the change from this title alone.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues Check ✅ Passed The code changes directly address all requirements from issue #5511. The PR extracts router.options.ssr.nonce and systematically propagates it across multiple layers: HeadContent files now pass nonce to all head-generated tags (meta, link, style, script), Scripts files augment asset and inline script attributes with the nonce, and ssr-client.ts extracts the nonce from the meta tag during hydration to make it available throughout the application. These changes ensure that all inline scripts produced by the framework receive the configured nonce attribute, resolving the CSP violation described in the issue where some inline scripts were missing the nonce.
Out of Scope Changes Check ✅ Passed All code changes remain directly in scope for addressing issue #5511. The modifications span HeadContent, Scripts, Asset, and ssr-client files across both react-router and solid-router packages, all of which are necessary to ensure nonce propagation to inline scripts and head elements. No unrelated changes such as formatting improvements, refactoring of unrelated features, or fixes to separate issues are present in this changeset.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7adb203 and ae3976a.

📒 Files selected for processing (3)
  • packages/react-router/src/HeadContent.tsx (8 hunks)
  • packages/react-router/src/Scripts.tsx (4 hunks)
  • packages/solid-router/src/HeadContent.tsx (9 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Oct 17, 2025

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit ae3976a

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 6m 17s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 3s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-17 23:17:07 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 17, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5522

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5522

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5522

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5522

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@5522

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5522

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5522

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5522

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5522

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5522

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5522

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5522

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5522

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5522

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5522

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5522

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5522

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5522

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5522

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5522

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5522

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5522

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5522

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5522

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5522

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5522

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5522

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5522

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5522

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@5522

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5522

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5522

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5522

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5522

commit: 7adb203

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-router/src/HeadContent.tsx (1)

16-70: Missing nonce in useMemo dependency array.

The useMemo uses nonce (line 47) and router.options.ssr?.nonce (lines 58-63) but only includes [routeMeta] in its dependency array (line 70). If the nonce value changes between renders, the memoized meta tags will contain a stale nonce.

Apply this diff to fix the dependency array:

    return resultMeta
- }, [routeMeta])
+ }, [routeMeta, nonce, router.options.ssr?.nonce])

Alternatively, since nonce is derived from router.options.ssr?.nonce, you could use just one:

    return resultMeta
- }, [routeMeta])
+ }, [routeMeta, nonce])
🧹 Nitpick comments (2)
packages/solid-router/src/Scripts.tsx (1)

8-8: LGTM: Nonce extraction looks good.

The nonce is correctly extracted from router options using optional chaining. When the nonce is undefined, Solid.js will skip rendering the attribute, which is the expected behavior.

For slightly cleaner code, you could conditionally spread the nonce only when defined:

-  attrs: { ...asset.attrs, nonce },
+  attrs: { ...asset.attrs, ...(nonce && { nonce }) },

And similarly at line 47:

   attrs: {
     ...script,
-    nonce,
+    ...(nonce && { nonce }),
   },

However, the current implementation is simpler and Solid handles undefined attributes correctly, so this is purely optional.

packages/react-router/src/HeadContent.tsx (1)

188-195: Consider reusing nonce from useTags hook.

The nonce is read twice: once in useTags (line 9) and again in HeadContent (line 191). While not incorrect, this is redundant. Consider threading the nonce through from useTags or deriving tags with nonce already embedded.

However, since Asset needs the nonce prop for link and style tags (lines 27, 33 in Asset.tsx), and the current approach is clear and explicit, this is a minor optimization opportunity rather than a defect.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f9b737 and 7adb203.

📒 Files selected for processing (6)
  • packages/react-router/src/Asset.tsx (2 hunks)
  • packages/react-router/src/HeadContent.tsx (6 hunks)
  • packages/react-router/src/Scripts.tsx (4 hunks)
  • packages/router-core/src/ssr/ssr-client.ts (1 hunks)
  • packages/solid-router/src/HeadContent.tsx (7 hunks)
  • packages/solid-router/src/Scripts.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/solid-router/src/Scripts.tsx
  • packages/react-router/src/HeadContent.tsx
  • packages/react-router/src/Asset.tsx
  • packages/react-router/src/Scripts.tsx
  • packages/solid-router/src/HeadContent.tsx
  • packages/router-core/src/ssr/ssr-client.ts
packages/{react-router,solid-router}/**

📄 CodeRabbit inference engine (AGENTS.md)

Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Files:

  • packages/solid-router/src/Scripts.tsx
  • packages/react-router/src/HeadContent.tsx
  • packages/react-router/src/Asset.tsx
  • packages/react-router/src/Scripts.tsx
  • packages/solid-router/src/HeadContent.tsx
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep framework-agnostic core router logic in packages/router-core/

Files:

  • packages/router-core/src/ssr/ssr-client.ts
🧬 Code graph analysis (1)
packages/react-router/src/Scripts.tsx (1)
packages/react-router/src/Asset.tsx (1)
  • Asset (11-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (5)
packages/solid-router/src/HeadContent.tsx (3)

10-10: LGTM! Nonce correctly extracted and applied to meta tags.

The nonce is properly extracted from router options and threaded into meta tag attributes.

Also applies to: 49-49


144-144: LGTM! Nonce correctly applied to styles and scripts.

The nonce is properly added to both style and script tags, which are the critical elements for Content Security Policy enforcement. This directly addresses the issue where inline scripts lacked nonce attributes.

Also applies to: 161-161


60-68: No issues found — this is an intentional framework pattern.

Verification confirms the custom meta tag with property: 'csp-nonce' is intentional. The client-side code in packages/router-core/src/ssr/ssr-client.ts explicitly queries this meta tag (document.querySelector('meta[property="csp-nonce"]')) and extracts the nonce value during hydration. This pattern is consistent across both solid-router and react-router and serves as the framework's mechanism for passing the nonce from server to client during SSR.

packages/solid-router/src/Scripts.tsx (1)

43-50: LGTM: Inline script nonce propagation is correct.

The nonce is correctly added to inline scripts, ensuring CSP compliance for all dynamically generated script tags. The spread order ensures the nonce from router options takes precedence, which is the correct behavior.

packages/react-router/src/Asset.tsx (1)

37-37: LGTM: Script nonce handling simplified.

The nonce is correctly threaded through attrs rather than as a separate prop, aligning with the Script component's implementation that reads from attrs.nonce (line 98).

Comment on lines +89 to +95
const meta = document.querySelector('meta[property="csp-nonce"]') as
| HTMLMetaElement
| undefined
const nonce = meta?.content
router.options.ssr = {
nonce,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential data loss: entire router.options.ssr object is overwritten.

Assigning router.options.ssr = { nonce } discards any pre-existing properties on router.options.ssr. If other parts of the hydration flow or user configuration set additional properties on this object, they will be lost.

Apply this diff to preserve existing properties:

  const meta = document.querySelector('meta[property="csp-nonce"]') as
    | HTMLMetaElement
    | undefined
  const nonce = meta?.content
- router.options.ssr = {
-   nonce,
- }
+ router.options.ssr = {
+   ...router.options.ssr,
+   nonce,
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const meta = document.querySelector('meta[property="csp-nonce"]') as
| HTMLMetaElement
| undefined
const nonce = meta?.content
router.options.ssr = {
nonce,
}
const meta = document.querySelector('meta[property="csp-nonce"]') as
| HTMLMetaElement
| undefined
const nonce = meta?.content
router.options.ssr = {
...router.options.ssr,
nonce,
}
🤖 Prompt for AI Agents
In packages/router-core/src/ssr/ssr-client.ts around lines 89 to 95, the code
unconditionally assigns router.options.ssr = { nonce } which overwrites any
existing properties; instead merge the nonce into the existing object
(preserving existing properties) by shallow-merging into router.options.ssr (or
initializing it if undefined) so the final object contains prior keys plus the
nonce.

tag: 'link',
attrs: {
...link,
nonce,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Nonce applied to constructed links, but missing on preload and asset links.

While constructed links receive the nonce attribute (line 84), both:

  • Preload links (modulepreload) at lines 119-125
  • Manifest assets (link tags) at lines 97-102

do not receive the nonce. This inconsistency may cause CSP violations if those links contain resources that require nonce validation.

Apply this diff to add nonce to preload links:

           router.ssr?.manifest?.routes[route.id]?.preloads
             ?.filter(Boolean)
             .forEach((preload) => {
               preloadMeta.push({
                 tag: 'link',
                 attrs: {
                   rel: 'modulepreload',
                   href: preload,
+                  nonce,
                 },
               })
             }),

And this diff to add nonce to manifest assets:

       .map(
         (asset) =>
           ({
             tag: 'link',
-            attrs: asset.attrs,
+            attrs: {
+              ...asset.attrs,
+              nonce,
+            },
           }) satisfies RouterManagedTag,
       )

Comment on lines 23 to 29
.forEach((asset) => {
assetScripts.push({
tag: 'script',
attrs: asset.attrs,
attrs: { ...asset.attrs, nonce },
children: asset.children,
} as any)
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify the type assertion and ensure type safety.

The as any cast at line 28 suggests a type mismatch between the constructed object and RouterManagedTag. This bypasses TypeScript's type checking and could hide issues.

Please verify:

  1. Does the RouterManagedTag type properly account for the nonce attribute in script tags?
  2. Can the type assertion be removed by updating the type definition?

Run the following script to check the RouterManagedTag type definition:


🏁 Script executed:

#!/bin/bash
# Description: Find the RouterManagedTag type definition to verify if nonce is properly typed

# Search for RouterManagedTag type definition
ast-grep --pattern 'type RouterManagedTag = $$$'

# Also search for interface definition
ast-grep --pattern 'interface RouterManagedTag {
  $$$
}'

# Search for nonce in type definitions
rg -n --type=ts 'RouterManagedTag' -A 10 -B 2

Length of output: 30501


Replace as any with type-safe pattern for consistency.

The RouterManagedTag type properly supports the nonce attribute through its generic attrs?: Record<string, any>. The as any assertion is unnecessary and bypasses type checking. The codebase already uses the satisfies RouterManagedTag pattern elsewhere (e.g., HeadContent.tsx lines 86, 102). For consistency and type safety per the coding guidelines, replace the as any with:

} satisfies RouterManagedTag)

Alternatively, remove the assertion entirely, since other scripts in react-router follow this approach without needing individual assertions.

🤖 Prompt for AI Agents
In packages/solid-router/src/Scripts.tsx around lines 23 to 29, the object
pushed into assetScripts is asserted with "as any", which bypasses type
checking; replace that assertion with a type-safe pattern by using "satisfies
RouterManagedTag" on the object literal (or remove the assertion entirely like
other script entries) so the nonce attribute is correctly type-checked and the
codebase remains consistent.

@schiller-manuel schiller-manuel merged commit 20d3501 into main Oct 17, 2025
3 of 5 checks passed
@schiller-manuel schiller-manuel deleted the fix-5511 branch October 17, 2025 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Start] router.options.ssr.nonce does not apply to all inline scripts

2 participants