Skip to content

Conversation

@danielroe
Copy link
Member

@danielroe danielroe commented Jan 4, 2026

resolves #14274
resolves nuxt/nuxt#34016

we were not checking reserved props at hydration stage (even though we were when rendering in SSR)

Summary by CodeRabbit

  • Bug Fixes

    • Fix SSR hydration so refs on custom elements are not rendered as HTML attributes; reserved element properties are no longer applied as attributes during hydration.
  • Tests

    • Added test coverage validating SSR hydration of custom elements with refs.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Hydration was adjusted to avoid applying reserved props (like ref) as attributes on custom elements during SSR hydration. A test was added that verifies a ref on a custom element is not rendered as an attribute and that the ref points to the element after hydration.

Changes

Cohort / File(s) Summary
Hydration Logic
packages/runtime-core/src/hydration.ts
Add guard to skip patchProp for custom elements when the prop key is a reserved prop (e.g., ref), preventing reserved props from being emitted as attributes during hydration.
Hydration Tests
packages/runtime-core/__tests__/hydration.spec.ts
New SSR hydration test ensuring a ref on a custom element is not rendered as a ref attribute and that the ref resolves to the actual element after hydration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

ready to merge, :hammer: p3-minor-bug, scope: custom elements, scope:hydration

Poem

🐰 I nudged a stray ref from a custom shell,
No more [object Object] in the HTML well.
Hydration now skips what shouldn't be shown,
The DOM is tidy — the rabbit has grown. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: skipping patching of reserved props for custom elements during hydration.
Linked Issues check ✅ Passed The PR directly addresses the linked issues by preventing reserved props from being patched on custom elements during hydration, matching SSR behavior and eliminating the ref="[object Object]" artifact.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the hydration behavior for reserved props on custom elements; no unrelated modifications were introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5c2f8e and 6153b49.

📒 Files selected for processing (1)
  • packages/runtime-core/__tests__/hydration.spec.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-core/__tests__/hydration.spec.ts (1)
packages/runtime-dom/src/index.ts (1)
  • createSSRApp (148-165)
🪛 ast-grep (0.40.3)
packages/runtime-core/__tests__/hydration.spec.ts

[warning] 1602-1602: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: container.innerHTML = 'hello'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 1602-1602: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: container.innerHTML = 'hello'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

⏰ 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: test / unit-test-windows
  • GitHub Check: test / e2e-test
🔇 Additional comments (1)
packages/runtime-core/__tests__/hydration.spec.ts (1)

1600-1616: LGTM! Test correctly validates the hydration fix for reserved props.

The test properly verifies that:

  1. The ref prop is not serialized as a DOM attribute during hydration (avoiding ref="[object Object]" in the output)
  2. The ref assignment still functions correctly, with root.value pointing to the actual element

This directly addresses the core issue described in #14274.

Note: The static analysis warnings about innerHTML usage are false positives. In a hydration test file, assigning hardcoded HTML strings to innerHTML is the standard pattern for setting up SSR-rendered content, and there's no XSS risk with these controlled test fixtures.


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 and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 4, 2026

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 103 kB (+7 B) 39.1 kB (+1 B) 35.2 kB (-3 B)
vue.global.prod.js 161 kB (+7 B) 59.1 kB (+1 B) 52.6 kB (-9 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47 kB 18.4 kB 16.8 kB
createApp 55.2 kB 21.4 kB 19.6 kB
createSSRApp 59.4 kB (+7 B) 23.2 kB (+2 B) 21.1 kB (-7 B)
defineCustomElement 60.7 kB 23.1 kB 21.1 kB
overall 69.5 kB 26.7 kB 24.3 kB

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 4, 2026

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14275
npm i https://pkg.pr.new/@vue/compiler-core@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14275
npm i https://pkg.pr.new/@vue/compiler-dom@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14275
npm i https://pkg.pr.new/@vue/compiler-sfc@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14275
npm i https://pkg.pr.new/@vue/compiler-ssr@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14275
npm i https://pkg.pr.new/@vue/reactivity@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14275
npm i https://pkg.pr.new/@vue/runtime-core@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14275
npm i https://pkg.pr.new/@vue/runtime-dom@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14275
npm i https://pkg.pr.new/@vue/server-renderer@14275
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14275
npm i https://pkg.pr.new/@vue/shared@14275
yarn add https://pkg.pr.new/@vue/[email protected]

vue

pnpm add https://pkg.pr.new/vue@14275
npm i https://pkg.pr.new/vue@14275
yarn add https://pkg.pr.new/[email protected]

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14275
npm i https://pkg.pr.new/@vue/compat@14275
yarn add https://pkg.pr.new/@vue/[email protected]

commit: 6153b49

Copy link

@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

🧹 Nitpick comments (1)
packages/runtime-core/__tests__/hydration.spec.ts (1)

1600-1616: LGTM! Test correctly verifies the fix for issue #14274.

The test appropriately verifies that reserved props (specifically ref) are not rendered as attributes on custom elements during hydration. The test structure is sound and follows existing patterns in the file.

Optional: Verify ref functionality still works

Consider adding an assertion to confirm the ref is correctly assigned, ensuring the fix doesn't break ref functionality:

   app.mount(container)
   expect(container.innerHTML).toBe('<my-element>hello</my-element>')
   expect((container.firstChild as Element).hasAttribute('ref')).toBe(false)
+  expect(root.value).toBe(container.firstChild)
 })

This would confirm both that the ref attribute is not added AND that the ref mechanism still functions correctly.

Note: Static analysis warnings about innerHTML assignment are false positives in this test context—this is safe fixture setup with known HTML content.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4ed61c and e5c2f8e.

📒 Files selected for processing (1)
  • packages/runtime-core/__tests__/hydration.spec.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-core/__tests__/hydration.spec.ts (1)
packages/runtime-dom/src/index.ts (1)
  • createSSRApp (148-165)
🪛 ast-grep (0.40.3)
packages/runtime-core/__tests__/hydration.spec.ts

[warning] 1602-1602: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: container.innerHTML = 'hello'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 1602-1602: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: container.innerHTML = 'hello'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

⏰ 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). (1)
  • GitHub Check: test / e2e-test

@danielroe
Copy link
Member Author

danielroe commented Jan 4, 2026

I don't believe the test failure is related as it passed on the previous commit, with no code changes

edit: and now, the test failure is on the pkg.pr.new end

@edison1105
Copy link
Member

/ecosystem-ci run

@edison1105 edison1105 added ready to merge The PR is ready to be merged. 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. scope:hydration labels Jan 5, 2026
@vue-bot
Copy link
Contributor

vue-bot commented Jan 5, 2026

📝 Ran ecosystem CI: Open

suite result latest scheduled
radix-vue success success
language-tools failure failure
vite-plugin-vue success success
nuxt success success
vue-i18n failure failure
vant success success
pinia success success
quasar success success
vue-simple-compiler success success
router success success
vitepress success success
test-utils success success
vueuse success success
vue-macros failure failure
vuetify failure failure
primevue success success

@Allmgreater
Copy link

Allmgreater commented Jan 6, 2026 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready to merge The PR is ready to be merged. scope:hydration

Projects

None yet

4 participants