Skip to content

SSR memory leak after a render error in a dynamic component #15070

Closed

Description

@cernymatej

Vue version

3.5.39

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-qfkzyzwp?file=leak.mjs

Steps to reproduce

  • download the reproduction from stackblitz (won't work there, needs to be run locally)
  • run pnpm i && pnpm leak
  • see the heap size & alive app instances grow with each request after the single failed render

The script renders 500 fresh SSR apps, which all get garbage-collected. Then renders one app whose slot content throws, then renders 500 more identical healthy apps, which will never be garbage collected afterwards.

A dynamic component <component :is> with a <slot /> is needed for this to reproduce.

Card

<script setup>
    defineProps({ tag: { type: String, default: 'div' } })
</script>

<template>
    <component :is="tag" class="card">
        <slot />
    </component>
</template>

app:

<script setup>
    defineProps(['payload'])  // one request renders with `payload: null` → throws
</script>

<template>
    <main>
        <Card>{{ payload.title }}</Card>
    </main>
</template>

What is expected?

A failed renderToString should not affect subsequent renders and all apps rendered after it should get garbage collected.

What is actually happening?

Every app created after the single failed render is kept in memory forever.

--- phase 1: 500 healthy renders ---
after 500: created=500 finalized=500 alive=0 heap=20.6MB

--- one crashing render (SSR render error) ---
render rejected as expected: Cannot read properties of null (reading 'title')

--- phase 2: 500 healthy renders (identical to phase 1) ---
after 500: created=1001 finalized=500 alive=501 heap=29.0MB

FINAL: created=1001 finalized=500 alive=501

System Info

Any additional comments?

No response

Activity

  1. edison1105 commented on Jul 12, 2026

    @edison1105
    Member

    I’m trying to understand the real-world expectation here.

    The leak only starts after renderToString() has already failed with an unhandled render error. In that situation, wouldn’t the application need to fix or handle the render error—or restart the SSR worker—rather than continue serving from the same process? If the render error does not occur, there is no leak.

    Do you have a production scenario where such render errors are expected and the same worker must continue serving requests afterward?

  2. cernymatej commented on Jul 12, 2026

    @cernymatej
    ContributorAuthor

    @edison1105 yes, I'm pretty sure this was the cause of a memory leak I had in one of my Nuxt apps in production

    Image Image

    It's quite easy to do property access on null, but maybe not that common for it to be in a dynamic component's slot - otherwise this would be a much more common leak.

    Here is a Nuxt reproduction, if you want to check it out: https://stackblitz.com/edit/github-izbyovlx?file=app%2Fcomponents%2FCard.vue,app%2Fapp.vue

    • just spam it with requests and have a look at the heap
    • every Vue app stays in memory indefinitely after the first crash, but it doesn't stop Nuxt from being able to serve more requests
    Image
  3. added
    🔨 p3-minor-bugPriority 3: this fixes a bug, but is an edge case that only affects very specific usage.
    on Jul 13, 2026
  4. added a commit that references this issue on Jul 16, 2026
    ddc132d
  5. locked and limited conversation to collaborators on Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    🔨 p3-minor-bugPriority 3: this fixes a bug, but is an edge case that only affects very specific usage.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions