ITADN

[Bug Report][4.1.6] VFileUpload crashes SSR (renderToString) when file list is empty

#23086Opengergely-xyz 创建于 24 天前
S: triage
G
gergely-xyzcommented
# Environment - Vuetify version: 4.1.6 (also reproduces on the latest published version, 4.1.7) - Vue version: 3.5.40 - Node version: v24.18.1 - Browser: N/A — server-side rendering - OS: Linux # Steps to reproduce Server-render a `VFileUpload` while its file list is empty and no `default` slot is provided (the normal state on first render, before the user picks any files). Minimal reproduction using only `vue`, `@vue/server-renderer`, and `vuetify` (no bundler/Nuxt needed — install those three packages and run with Node's ESM loader, stubbing `.css` imports): ```js // repro.mjs import { createSSRApp, h } from 'vue' import { renderToString } from '@vue/server-renderer' import { createVuetify } from 'vuetify' import { VFileUpload } from 'vuetify/components' const app = createSSRApp({ render() { return h(VFileUpload, { modelValue: [] }) } }) app.use(createVuetify()) await renderToString(app) // throws ``` ```js // css-stub-loader.mjs (needed only because this repro imports vuetify components directly, bypassing a bundler) export async function load(url, context, nextLoad) { if (url.endsWith('.css')) { return { format: 'module', source: 'export default {}', shortCircuit: true } } return nextLoad(url, context) } ``` ``` node --experimental-loader ./css-stub-loader.mjs repro.mjs ``` This also reproduces identically inside a real Nuxt 4 SSR app (`ssr: true`) when a page renders a `<v-file-upload>` with an empty `v-model` array and no `#default` slot — which is the normal state before any file is selected. # Expected Behavior `VFileUpload` renders on the server the same empty-dropzone markup it renders on the client, with no error. # Actual Behavior Server rendering throws: ``` TypeError: Cannot read properties of null (reading 'length') at renderVNodeChildren (@vue/server-renderer/dist/server-renderer.cjs.js:574:32) at renderVNode (@vue/server-renderer/dist/server-renderer.cjs.js:547:7) at renderComponentSubTree (@vue/server-renderer/dist/server-renderer.cjs.js:510:7) ... ``` In a Nuxt app this surfaces as a 500 response (wrapped in an `H3Error`) for any page that server-renders a `v-file-upload` before a file has been chosen. # Root cause [`packages/vuetify/src/components/VFileUpload/VFileUploadList.tsx`](https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VFileUpload/VFileUploadList.tsx) has: ```tsx if (!slots.default && !files.length) return (<></>) ``` The JSX empty-fragment shorthand `<></>` compiles to a `Fragment` VNode with `children: null` (visible in the published `lib/components/VFileUpload/VFileUploadList.js`: `createElementVNode(Fragment, null, null)`). Vue's client renderer tolerates a `null` children on a Fragment vnode, but `@vue/server-renderer`'s `renderVNode` unconditionally forwards a Fragment's `children` into `renderVNodeChildren`, which does `children.length` without a null check: ```js // @vue/server-renderer case Vue.Fragment: ... renderVNodeChildren(push, children, parentComponent, slotScopeId) ... function renderVNodeChildren(push, children, parentComponent, slotScopeId) { for (let i = 0; i < children.length; i++) { ... } // throws when children is null } ``` `VFileUploadList` is always rendered internally by `VFileUpload` whenever `insetFileList` is false (the default) and no top-level `default` slot is passed to `VFileUpload` — and it is never itself given a `default` slot by its parent, so `!slots.default` is always true in that configuration. Combined with an empty file list (`!files.length`, true on first render before any file is picked), the broken branch is hit on essentially every SSR render of a default-configured `v-file-upload`. # Suggested fix Return an empty array instead of an empty JSX fragment, e.g.: ```tsx if (!slots.default && !files.length) return (<>{[]}</>) ``` or fix it generically in `@vue/server-renderer`'s `renderVNodeChildren` to treat `null`/`undefined` children as zero children. # Workaround Passing `inset-file-list` to `<v-file-upload>` avoids the bug, since it changes `VFileUpload` to render the file list inline in the dropzone instead of instantiating `VFileUploadList` at all — but this also changes the file list's visual placement/styling, so it's a workaround rather than a fix.
0 条评论