[Bug Report][4.1.6] VFileUpload crashes SSR (renderToString) when file list is empty
S: triage
# 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 条评论