Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Jan 26, 2026

not human review yet

About 2x performance on useTags

before
Screenshot 2026-01-26 at 09 29 21

after
Screenshot 2026-01-26 at 09 29 25

Summary by CodeRabbit

  • Refactor
    • Improved assembly and ordering of page head content with per-type deduplication for titles, meta, JSON‑LD, links, preloads, styles, and scripts.
    • CSP nonces are preserved and injected into generated head elements.
  • New Features
    • LD+JSON entries, links, preloads, styles and scripts are deduplicated more reliably to prevent duplicates.
    • Script and style entries now support inline children/content alongside attributes for richer inline tags.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

Separates and reorders head tag collection into per-type aggregation (title, ld+json, meta, links, preloads, styles, head scripts) with backward-iteration deduplication and nonce propagation; updates RouteMatchExtensions typings to allow optional children on script/style elements; removes the exported uniqBy signature.

Changes

Cohort / File(s) Summary
Head content utilities
packages/react-router/src/headContentUtils.tsx
Rewrote head/tag aggregation to multi-pass per-type collection and backward deduplication (title, ld+json by JSON, meta by name/property+content+media). Rebuilt links, preload links, styles, and head scripts with composite-key dedupe and CSP nonce injection. Removed exported uniqBy declaration.
React Matches typings
packages/react-router/src/Matches.tsx
Broadened RouteMatchExtensions typings: scripts, styles, and headScripts arrays now allow elements intersected with an optional `children?: string
Solid Matches typings
packages/solid-router/src/Matches.tsx
Same typing change as React: module augmentation updates scripts, styles, and headScripts to permit an optional children property on elements.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

"🐰 I hop through tags with careful pace,
Titles, metas, scripts in place.
Nonces snug like carrots near,
Deduped and tidy — give a cheer! 🥕"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: a refactor targeting useTags performance in the react-router package, which aligns with the PR's objective of improving performance.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@nx-cloud
Copy link

nx-cloud bot commented Jan 26, 2026

🤖 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 dadb39d

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

☁️ Nx Cloud last updated this comment at 2026-01-26 10:04:22 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 26, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6521

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6521

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6521

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: dadb39d

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: 2

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/headContentUtils.tsx (1)

66-81: Empty key causes unintended deduplication of meta tags without name or property.

When a meta tag lacks both name and property attributes (e.g., <meta charset="utf-8"> or <meta http-equiv="..."/>), the key defaults to ''. This causes all such meta tags to deduplicate against each other, keeping only the first one encountered.

Additionally, spreading nonce into meta tag attrs (line 78) is unusual—nonce is typically only meaningful for <script> and <style> elements, not <meta>.

🐛 Proposed fix for empty key deduplication
        } else {
          // Deduplicate by name or property attribute
          const key = m.name ?? m.property ?? ''
-         if (key) {
-           if (seenMeta.has(key)) continue
-           seenMeta.add(key)
-         }
+         // Only deduplicate if we have a meaningful key
+         if (key !== '') {
+           if (seenMeta.has(key)) continue
+           seenMeta.add(key)
+         }

          metaTags.push({
            tag: 'meta',
            attrs: {
              ...m,
-             nonce,
            },
          })
        }
🤖 Fix all issues with AI agents
In `@packages/react-router/src/headContentUtils.tsx`:
- Around line 231-237: The dedupe key generation inside the loop over
matchScripts (in headContentUtils.tsx) can produce an empty string when both
script.attrs.src and script.children are missing/empty, causing unintended
deduplication; update the loop in the block that builds key (where { children,
...attrs } = script and key = attrs.src ?? String(children ?? '')) to first
derive a normalizedKey (trim strings) and then skip processing (continue) if
normalizedKey is empty — or alternatively treat scripts with no meaningful key
as non-dedupe candidates by not checking/adding them to the seen set; adjust the
logic around seen.has(key)/seen.add(key) accordingly so only non-empty keys are
deduplicated.
- Around line 265-275: The function uniqBy is dead code after the refactor and
should be removed; delete the exported function uniqBy from headContentUtils.tsx
and any related export references, and ensure there are no lingering
imports/usages (e.g., check useTags and other helpers in this file) so the file
builds cleanly; if this was part of a public API intentionally kept, instead
align its implementation with the other routers by replacing it with an
arr.filter-based implementation, but otherwise remove the uniqBy declaration and
its export to eliminate the unused code.

Comment on lines 231 to 237
for (const script of matchScripts) {
if (!script) continue
// Deduplicate by src (external) or children (inline) before creating the object
const { children, ...attrs } = script
const key = attrs.src ?? String(children ?? '')
if (seen.has(key)) continue
seen.add(key)
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 | 🟡 Minor

Empty deduplication key when both src and children are empty.

If a script has neither src nor children (or both are empty), the key becomes '', causing all such scripts to deduplicate against each other. Consider skipping deduplication for scripts without a meaningful key, or skip invalid scripts entirely.

🔧 Suggested fix
          const { children, ...attrs } = script
          const key = attrs.src ?? String(children ?? '')
+         // Only deduplicate if we have a meaningful key
+         if (key === '') continue // Skip scripts with no src or content
          if (seen.has(key)) continue
          seen.add(key)
🤖 Prompt for AI Agents
In `@packages/react-router/src/headContentUtils.tsx` around lines 231 - 237, The
dedupe key generation inside the loop over matchScripts (in
headContentUtils.tsx) can produce an empty string when both script.attrs.src and
script.children are missing/empty, causing unintended deduplication; update the
loop in the block that builds key (where { children, ...attrs } = script and key
= attrs.src ?? String(children ?? '')) to first derive a normalizedKey (trim
strings) and then skip processing (continue) if normalizedKey is empty — or
alternatively treat scripts with no meaningful key as non-dedupe candidates by
not checking/adding them to the seen set; adjust the logic around
seen.has(key)/seen.add(key) accordingly so only non-empty keys are deduplicated.

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: 0

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/headContentUtils.tsx (1)

67-93: Fix meta deduplication for charset/httpEquiv/itemProp and align CSP nonce checks.

Line 68 dedupes only by name/property, content, and media. Meta tags that rely on charset, httpEquiv, or itemProp will all collapse into the same key and get dropped. Also, the CSP nonce check (seenMeta.has('csp-nonce')) doesn’t match the composite key format, so duplicates can slip in.

🔧 Suggested fix
-          const key = `${m.name ?? m.property}\0${m.content}\0${m.media}`
-          if (key) {
-            if (seenMeta.has(key)) continue
-            seenMeta.add(key)
-          }
+          const dedupeId =
+            m.name ??
+            m.property ??
+            (m.httpEquiv ? `http-equiv:${m.httpEquiv}` : undefined) ??
+            (m.charset ? `charset:${m.charset}` : undefined) ??
+            m.itemProp
+          const key = dedupeId
+            ? `${dedupeId}\0${m.content ?? ''}\0${m.media ?? ''}`
+            : null
+          if (key) {
+            if (seenMeta.has(key)) continue
+            seenMeta.add(key)
+          }
@@
-    if (nonce && !seenMeta.has('csp-nonce')) {
+    const cspKey = nonce ? `csp-nonce\0${nonce}\0` : null
+    if (nonce && (!cspKey || !seenMeta.has(cspKey))) {
       metaTags.push({
         tag: 'meta',
         attrs: {
           property: 'csp-nonce',
           content: nonce,
         },
       })
+      if (cspKey) seenMeta.add(cspKey)
     }
♻️ Duplicate comments (1)
packages/react-router/src/headContentUtils.tsx (1)

232-246: Avoid deduping scripts with no meaningful key.

If both src and children are empty, the key becomes a generic string (e.g., "undefined\0undefined\0undefined"), causing unrelated scripts to dedupe against each other. This was previously flagged and still applies.

🔧 Suggested fix
           const { children, ...attrs } = script
-          const key = `${attrs.src}\0${attrs.type}\0${children}`
-          if (seen.has(key)) continue
-          seen.add(key)
+          const hasKey = attrs.src || children
+          if (hasKey) {
+            const key = `${attrs.src ?? ''}\0${attrs.type ?? ''}\0${children ?? ''}`
+            if (seen.has(key)) continue
+            seen.add(key)
+          }
           result.push({

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.

2 participants