fix(ssr): filter out transition-specific props to avoid invalid HTML attributes
Summary:
- Fixed TransitionGroup passing transition-related props to DOM elements incorrectly
- Added prop filtering to exclude transition-specific and component-specific attributes (tag, moveClass)
- Resolved W3C validation errors with invalid HTML attributes
Changes: Filtered props before passing to createVNode, excluding transition props and tag/moveClass while retaining valid HTML attributes.
const filteredProps: Record<string, any> = {}
for (const key in rawProps) {
if (!(key in TransitionPropsValidators) && key !== 'tag' && key !== 'moveClass') {
filteredProps[key] = (rawProps as any)[key]
}
}
return createVNode(tag, filteredProps, children)
Fixes: #13037, W3C validation errors. No breaking changes.
Summary by CodeRabbit
-
Bug Fixes
- SSR now omits transition-related props from rendered HTML for transition-group (supports both kebab- and camel-case, including move-class), and ensures consistent attribute output for static, dynamic, and mixed v-bind scenarios with an attrs fallback when needed.
-
Tests
- Added comprehensive tests covering transition prop filtering and the updated SSR attribute rendering behavior.
Walkthrough
Reintroduces TransitionGroup prop filtering on the SSR side by updating the SSR compiler transform to mark transition contexts and pass an isTransition flag to ssrRenderAttrs, and by extending the server renderer's ssrRenderAttrs to skip transition-related props (kebab and camel forms) when isTransition is true. Tests updated/added accordingly.
Changes
| Cohort / File(s) | Summary |
|---|---|
SSR Compiler Transform: TransitionGrouppackages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts |
Reconstructs runtime-like Transition props validators, adds kebabToCamel, uses hasOwn to detect attributes, filters transition-related attributes (including moveClass/move-class) in phase 1, stores tag/propsExp in WIP for phase 2, and emits _ssrRenderAttrs(..., tagOrExpr, true) when appropriate. |
Server Renderer: Attributes Helperpackages/server-renderer/src/helpers/ssrRenderAttrs.ts |
Extends ssrRenderAttrs signature to (props, tag?, isTransition?); adds transitionPropsToFilter and kebabToCamel logic to skip transition-specific keys (both camelCase and kebab-case) when isTransition is true. |
Tests: Compiler SSR TransitionGrouppackages/compiler-ssr/__tests__/ssrTransitionGroup.spec.ts |
Updates inline snapshots to reflect _ssrRenderAttrs(..., tagOrExpr, true) usage and adds tests covering transition-prop filtering, moveClass handling, static/dynamic tags, v-bind scenarios, event handler omission, and mixed bindings. |
Tests: Server Renderer Attributespackages/server-renderer/__tests__/ssrRenderAttrs.spec.ts |
Adds tests asserting transition-prop filtering when isTransition is true and preserving all props when isTransition is false/undefined. |
Sequence Diagram(s)
sequenceDiagram
autonumber
participant Template as SFC Template
participant Compiler as compiler-ssr (ssrTransformTransitionGroup)
participant Renderer as server-renderer (ssrRenderAttrs)
participant Output as SSR Output
Template->>Compiler: compile <TransitionGroup> (props, tag)
Note over Compiler: Phase 1 - collect tag, filter attrs (kebab & camel), set isTransition
Compiler->>Compiler: store WIP (tag, propsExp)
Compiler->>Renderer: call _ssrRenderAttrs(_attrs, tagOrExpr, true)
Note over Renderer: if isTransition=true -> skip Transition props (camel+kebab) & moveClass
Renderer-->>Compiler: filtered attributes string
Compiler-->>Output: emit HTML fragment with filtered attributes
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested labels
:hammer: p3-minor-bug, scope:hydration
Poem
I nibbled at tags in the moonlit glen,
Camel and kebab I chased down again.
I plucked stray props from the SSR feast,
Left lists tidy and markup at peace.
Hoppy renderings—clean and keen. 🐇✨
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 "fix(ssr): filter out transition-specific props to avoid invalid HTML attributes" concisely and accurately summarizes the main change—filtering transition-related props from SSR output to prevent invalid HTML attributes—and is specific enough for history scanning. |
| Linked Issues Check | ✅ Passed | The changes address issue [#13037] by filtering transition-specific props in both the SSR compiler output and the server-renderer (compiler-ssr now excludes transition props and ssrRenderAttrs gains an isTransition filter), and added tests cover name, moveClass, and v-bind scenarios so the SSR output no longer emits transition props as DOM attributes. |
| Out of Scope Changes Check | ✅ Passed | All modifications are confined to SSR compilation and runtime attribute rendering (packages/compiler-ssr, packages/server-renderer) and related tests; no unrelated modules were changed, although ssrRenderAttrs gained an optional isTransition parameter which is updated in call sites as part of this feature. |
| Docstring Coverage | ✅ Passed | No functions found in the changes. Docstring coverage check skipped. |
✨ Finishing touches
- [ ] 📝 Generate Docstrings
🧪 Generate unit tests
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
I don't think this change has fixed the problem.
Here's a Playground using this PR. The
nameattribute is still present in the generated HTML:As far as I'm aware, the original issue only occurs when SSR is enabled. The extra props are already removed during client-side rendering.
Perhaps I'm missing something. Are you able to provide a Playground (using the Netlify preview of this PR), or even better include some test cases that demonstrate the fix is working correctly?
Sorry, this does affect SSR after all. I had a misunderstanding of the code, and I'll fix this part again.
I don't think this change has fixed the problem.
Here's a Playground using this PR. The
nameattribute is still present in the generated HTML:As far as I'm aware, the original issue only occurs when SSR is enabled. The extra props are already removed during client-side rendering.
Perhaps I'm missing something. Are you able to provide a Playground (using the Netlify preview of this PR), or even better include some test cases that demonstrate the fix is working correctly?
During my local testing, I noticed that the actual effect is inconsistent with the display in the Vue SFC Playground. Currently, I’m not quite clear on how to present local effects through the Playground. However, I have integrated the build artifacts from Playground into the project for server-side rendering, and no issues occurred during this process.
What confuses me now is: since the same files are used, why are there discrepancies in the final build results?
P.S. The repository address where I used the aforementioned build artifacts is deploy-preview-13894-ssr-test.git
The SSR example you linked appears to be using a render function, so it won't match what happens in the Playground.
For components that use templates, Vue compiles a separate SSR version that bypasses VNodes and just generates the HTML string. You can see that version in the Playground by clicking on the SSR tab.
It would appear that this is where the spurious name attribute is being added to the output.
Also need to consider the usage of v-bind="prop".
see Playground with this PR