Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix(react): support raw content in Head#894

Open
harlan-zw wants to merge 3 commits into
mainunjs/unhead:mainfrom
fix/react-head-raw-contentunjs/unhead:fix/react-head-raw-contentCopy head branch name to clipboard
Open

fix(react): support raw content in Head#894
harlan-zw wants to merge 3 commits into
mainunjs/unhead:mainfrom
fix/react-head-raw-contentunjs/unhead:fix/react-head-raw-contentCopy head branch name to clipboard

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

No linked issue.

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

dangerouslySetInnerHTML is currently emitted as an attribute by the React <Head> adapter, leaving script, style, noscript, and title content empty. Map valid raw content to Unhead's content fields, reject conflicting shapes, and retain SSR breakout protection.

Summary by CodeRabbit

  • New Features

    • Added support for rendering raw HTML content in head elements, including scripts, styles, noscript sections, and titles.
    • Raw content now renders correctly in both the browser and server-rendered pages.
  • Bug Fixes

    • Prevented internal raw-content properties from appearing as HTML attributes.
    • Added validation for invalid combinations, malformed content, and unsupported self-closing tags.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@harlan-zw, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dff49244-9a58-4763-8872-052c6583caf6

📥 Commits

Reviewing files that changed from the base of the PR and between 686d7d2 and cf04959.

📒 Files selected for processing (1)
  • packages/react/src/components.ts
📝 Walkthrough

Walkthrough

The React Head component now supports validated dangerouslySetInnerHTML content for script, style, noscript, and title elements, mapping content for DOM and SSR rendering while rejecting invalid combinations and self-closing tags.

Changes

React raw head content

Layer / File(s) Summary
Raw content normalization and integration
packages/react/src/components.ts
Extracts and validates dangerouslySetInnerHTML, rejects conflicting or self-closing usage, and maps content to textContent or innerHTML.
DOM and SSR coverage
packages/react/test/HeadRawContent.test.tsx, packages/react/test/HeadRawContentSSR.test.tsx
Verifies rendered raw content, prop removal, and errors for malformed values, children conflicts, and self-closing tags.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReactHead
  participant normalizeRawContent
  participant DOMOrSSRRenderer
  ReactHead->>normalizeRawContent: normalize head element props
  normalizeRawContent->>ReactHead: return textContent or innerHTML
  ReactHead->>DOMOrSSRRenderer: render normalized head input
  DOMOrSSRRenderer-->>ReactHead: produce DOM nodes or serialized HTML
Loading

Possibly related PRs

  • unjs/unhead#892: Modifies the same React Head element-processing pipeline by flattening React.Fragment children.
  • unjs/unhead#893: Modifies the same React head-prop serialization pathway to normalize React-style attribute aliases and filter React-only props.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, conventional, and clearly summarizes the main change to support raw content in Head.
Description check ✅ Passed The description matches the template with linked issue, change type, and a clear summary of the bug fix and its purpose.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/react-head-raw-content

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size

No notable changes

All bundles (14)
Bundle Gzipped Brotli Raw
Core
Client (Minimal) 5.4 kB 4.9 kB 13.6 kB
Client (Full) 9.5 kB 8.7 kB 24.9 kB
Client (Self-Contained) 5.7 kB 5.1 kB 14.3 kB
Server (Minimal) 4.9 kB 4.4 kB 12.3 kB
Server (Self-Contained) 5.2 kB 4.7 kB 13 kB
Vue
Vue Client (Minimal) 5.9 kB 5.3 kB 14.6 kB
Vue Client (Full) 10.5 kB 9.6 kB 27.7 kB
Vue Server (Minimal) 5.4 kB 4.9 kB 13.4 kB
React
React Client (Minimal) 5.8 kB 5.3 kB 14.7 kB
React Client (Full) 10.6 kB 9.6 kB 28.1 kB
React Server (Minimal) 5.2 kB 4.7 kB 13 kB
Schema.org
Schema.org (Minimal) 10.1 kB 9.2 kB 28 kB
Schema.org Imports 0.1 kB 0.1 kB 0.1 kB
Schema.org Vue Meta 0.4 kB 0.3 kB 0.8 kB

📦 Runtime Dependencies

No runtime dependency changes

All packages (10)
Package External deps Install size Largest dependency Skipped optional
@unhead/angular 10 773.6 kB @jridgewell/trace-mapping 146.7 kB 0
@unhead/bundler 18 5.6 MB @oxc-parser/binding-linux-arm64-gnu 2.2 MB 19
@unhead/cli 22 5.7 MB @oxc-parser/binding-linux-arm64-gnu 2.2 MB 19
@unhead/eslint-plugin 9 683.2 kB @jridgewell/trace-mapping 146.7 kB 0
@unhead/react 19 5.6 MB @oxc-parser/binding-linux-arm64-gnu 2.2 MB 19
@unhead/schema-org 9 683.2 kB @jridgewell/trace-mapping 146.7 kB 0
@unhead/solid-js 19 5.6 MB @oxc-parser/binding-linux-arm64-gnu 2.2 MB 19
@unhead/svelte 19 5.6 MB @oxc-parser/binding-linux-arm64-gnu 2.2 MB 19
@unhead/vue 19 5.6 MB @oxc-parser/binding-linux-arm64-gnu 2.2 MB 19
unhead 9 683.2 kB @jridgewell/trace-mapping 146.7 kB 0
Skipped optional dependencies (114)
  • @unhead/bundler: oxc-parser -> @oxc-parser/binding-android-arm-eabi, oxc-parser -> @oxc-parser/binding-android-arm64, oxc-parser -> @oxc-parser/binding-darwin-arm64, oxc-parser -> @oxc-parser/binding-darwin-x64, oxc-parser -> @oxc-parser/binding-freebsd-x64, oxc-parser -> @oxc-parser/binding-linux-arm-gnueabihf, oxc-parser -> @oxc-parser/binding-linux-arm-musleabihf, oxc-parser -> @oxc-parser/binding-linux-arm64-musl, oxc-parser -> @oxc-parser/binding-linux-ppc64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-musl, oxc-parser -> @oxc-parser/binding-linux-s390x-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-musl, oxc-parser -> @oxc-parser/binding-openharmony-arm64, oxc-parser -> @oxc-parser/binding-wasm32-wasi, oxc-parser -> @oxc-parser/binding-win32-arm64-msvc, oxc-parser -> @oxc-parser/binding-win32-ia32-msvc, oxc-parser -> @oxc-parser/binding-win32-x64-msvc
  • @unhead/cli: oxc-parser -> @oxc-parser/binding-android-arm-eabi, oxc-parser -> @oxc-parser/binding-android-arm64, oxc-parser -> @oxc-parser/binding-darwin-arm64, oxc-parser -> @oxc-parser/binding-darwin-x64, oxc-parser -> @oxc-parser/binding-freebsd-x64, oxc-parser -> @oxc-parser/binding-linux-arm-gnueabihf, oxc-parser -> @oxc-parser/binding-linux-arm-musleabihf, oxc-parser -> @oxc-parser/binding-linux-arm64-musl, oxc-parser -> @oxc-parser/binding-linux-ppc64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-musl, oxc-parser -> @oxc-parser/binding-linux-s390x-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-musl, oxc-parser -> @oxc-parser/binding-openharmony-arm64, oxc-parser -> @oxc-parser/binding-wasm32-wasi, oxc-parser -> @oxc-parser/binding-win32-arm64-msvc, oxc-parser -> @oxc-parser/binding-win32-ia32-msvc, oxc-parser -> @oxc-parser/binding-win32-x64-msvc
  • @unhead/react: oxc-parser -> @oxc-parser/binding-android-arm-eabi, oxc-parser -> @oxc-parser/binding-android-arm64, oxc-parser -> @oxc-parser/binding-darwin-arm64, oxc-parser -> @oxc-parser/binding-darwin-x64, oxc-parser -> @oxc-parser/binding-freebsd-x64, oxc-parser -> @oxc-parser/binding-linux-arm-gnueabihf, oxc-parser -> @oxc-parser/binding-linux-arm-musleabihf, oxc-parser -> @oxc-parser/binding-linux-arm64-musl, oxc-parser -> @oxc-parser/binding-linux-ppc64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-musl, oxc-parser -> @oxc-parser/binding-linux-s390x-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-musl, oxc-parser -> @oxc-parser/binding-openharmony-arm64, oxc-parser -> @oxc-parser/binding-wasm32-wasi, oxc-parser -> @oxc-parser/binding-win32-arm64-msvc, oxc-parser -> @oxc-parser/binding-win32-ia32-msvc, oxc-parser -> @oxc-parser/binding-win32-x64-msvc
  • @unhead/solid-js: oxc-parser -> @oxc-parser/binding-android-arm-eabi, oxc-parser -> @oxc-parser/binding-android-arm64, oxc-parser -> @oxc-parser/binding-darwin-arm64, oxc-parser -> @oxc-parser/binding-darwin-x64, oxc-parser -> @oxc-parser/binding-freebsd-x64, oxc-parser -> @oxc-parser/binding-linux-arm-gnueabihf, oxc-parser -> @oxc-parser/binding-linux-arm-musleabihf, oxc-parser -> @oxc-parser/binding-linux-arm64-musl, oxc-parser -> @oxc-parser/binding-linux-ppc64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-musl, oxc-parser -> @oxc-parser/binding-linux-s390x-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-musl, oxc-parser -> @oxc-parser/binding-openharmony-arm64, oxc-parser -> @oxc-parser/binding-wasm32-wasi, oxc-parser -> @oxc-parser/binding-win32-arm64-msvc, oxc-parser -> @oxc-parser/binding-win32-ia32-msvc, oxc-parser -> @oxc-parser/binding-win32-x64-msvc
  • @unhead/svelte: oxc-parser -> @oxc-parser/binding-android-arm-eabi, oxc-parser -> @oxc-parser/binding-android-arm64, oxc-parser -> @oxc-parser/binding-darwin-arm64, oxc-parser -> @oxc-parser/binding-darwin-x64, oxc-parser -> @oxc-parser/binding-freebsd-x64, oxc-parser -> @oxc-parser/binding-linux-arm-gnueabihf, oxc-parser -> @oxc-parser/binding-linux-arm-musleabihf, oxc-parser -> @oxc-parser/binding-linux-arm64-musl, oxc-parser -> @oxc-parser/binding-linux-ppc64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-musl, oxc-parser -> @oxc-parser/binding-linux-s390x-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-musl, oxc-parser -> @oxc-parser/binding-openharmony-arm64, oxc-parser -> @oxc-parser/binding-wasm32-wasi, oxc-parser -> @oxc-parser/binding-win32-arm64-msvc, oxc-parser -> @oxc-parser/binding-win32-ia32-msvc, oxc-parser -> @oxc-parser/binding-win32-x64-msvc
  • @unhead/vue: oxc-parser -> @oxc-parser/binding-android-arm-eabi, oxc-parser -> @oxc-parser/binding-android-arm64, oxc-parser -> @oxc-parser/binding-darwin-arm64, oxc-parser -> @oxc-parser/binding-darwin-x64, oxc-parser -> @oxc-parser/binding-freebsd-x64, oxc-parser -> @oxc-parser/binding-linux-arm-gnueabihf, oxc-parser -> @oxc-parser/binding-linux-arm-musleabihf, oxc-parser -> @oxc-parser/binding-linux-arm64-musl, oxc-parser -> @oxc-parser/binding-linux-ppc64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-gnu, oxc-parser -> @oxc-parser/binding-linux-riscv64-musl, oxc-parser -> @oxc-parser/binding-linux-s390x-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-gnu, oxc-parser -> @oxc-parser/binding-linux-x64-musl, oxc-parser -> @oxc-parser/binding-openharmony-arm64, oxc-parser -> @oxc-parser/binding-wasm32-wasi, oxc-parser -> @oxc-parser/binding-win32-arm64-msvc, oxc-parser -> @oxc-parser/binding-win32-ia32-msvc, oxc-parser -> @oxc-parser/binding-win32-x64-msvc

Production dependencies only. Peer dependencies and Unhead workspace packages are excluded. Skipped optional dependencies are unavailable on the CI platform.


⚡ Performance (directional)

No significant change (within CI noise)

All benchmarks (14)
Benchmark PR Δ RME
SSR render (CPU) 0.368 ms ~ noise ±9.7%
SSR render (wall) 0.265 ms ~ noise ±5.0%
SSR allocated / render 236 KiB ~ noise ±7.0%
Schema.org cached render (CPU) 0.343 ms ~ noise ±6.9%
Schema.org cached render (wall) 0.240 ms ~ noise ±3.1%
Schema.org cached allocated / render 133.8 KiB ~ noise ±0.9%
Streaming wrapStream drain (CPU) 0.252 ms ~ noise ±5.7%
Streaming wrapStream drain (wall) 0.145 ms ~ noise ±5.4%
Streaming allocated / drain 149.5 KiB ~ noise ±0.5%
Streaming suspense chunk (CPU) 0.013 ms ~ noise ±7.9%
Streaming allocated / suspense chunk 5.1 KiB ~ noise ±0.7%
CSR DOM mutations / nav 38 ~ noise
CSR re-render (CPU) 0.852 ms ~ noise ±5.2%
CSR re-render (wall) 0.484 ms ~ noise ±2.2%

Baseline: main @ d9b3d2a · 2026-07-26 · gzipped is the headline size metric · perf is directional (shared-runner, gated)

@harlan-zw harlan-zw added v3 Targets Unhead v3 or the main branch bug Something isn't working react React adapter and integration needs-rebase Conflicts with the target branch and needs a rebase labels Jul 24, 2026
…ntent

# Conflicts:
#	packages/react/src/components.ts
@harlan-zw harlan-zw removed the needs-rebase Conflicts with the target branch and needs a rebase label Jul 24, 2026
# Conflicts:
#	packages/react/src/components.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working react React adapter and integration v3 Targets Unhead v3 or the main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.