RouterView with Transition leads to memory leak and detached nodesΒ #2437
Description
Reproduction
Steps to reproduce the bug
- Use or set up the minimal reproduction
- Click between the "Home" and "About" links at the top a few times. Each page has 1000 divs on it, which accumulate as detached nodes and increased memory usage with each route change.
Expected behavior
I would expect the unmounted components to be cleaned up in garbage collection, but references must still exist to the component or its elements, leading to a memory leak and degraded performance over time.
Actual behavior
The nodes are retained (can be viewed in Chrome devtool's Heap Snapshots), leading to ever-increasing detached nodes (and other objects), which leads to increased memory use over time.
Additional information
I've created a minimal setup as follows.
App.vue, which sets up the boilerplate usage for RouterView with Transition (taken from the docs), along with the related CSS transitions (some notes on this below):
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView v-slot="{ Component }">
<Transition name="fade">
<component :is="Component" />
</Transition>
</RouterView>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 200ms;
}
.fade-leave-active {
position: absolute;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
HomeView.vue and AboutView.vue, two pages that are almost identical and that link to each other via a RouterLink, and which each generate 1,000 dummy divs to help illustrate the problem. Here is HomeView.vue:
<script setup lang="ts">
import { RouterLink } from 'vue-router';
</script>
<template>
<div id="HOME">
<RouterLink to="/about">About</RouterLink>
<div
v-for="index in 1000"
:key="index"
>
Hello Home {{ index }}
</div>
</div>
</template>
When the Transition component is present (under certain conditions, see more info below), the component elements (and other objects in other tests I've done) are retained as detached nodes and accumulate in memory, and not disposed of through garbage collection, implying references to them exist and that the component is not properly removed after the transition (I think).
On initial load, the heap snapshot in Chrome devtools looks like this (note the "detached" filter to focus on detached nodes):
Everything looks fine. After clicking the link once to switch to the next page, it looks like this (~1,000 detached nodes):
After clicking the links 4 more times (so 5 total route changes with page transitions), it looks like this (~5,000 detached nodes):
Note that if the Transition component is omitted this issue is no longer reproduced. When there are no more route transitions, there are still always 1,000 detached nodes (the number corresponding to a single page, which may or may not be good), but importantly the memory usage never goes up.
Notes:
- As I said earlier, this issue doesn't happen if there is no Transition component inside the RouterView;
- The Transition component alone is not enough to cause the issue: the duration matters. When no CSS matching the name is given (and so there is no visible transition) the behaviour is normal (no memory leak). When the duration is very low, it is also not an issue (I have tested with 0ms and 1ms with no issue, no memory leak). At 10ms and higher the issue is consistently reproducible. Obviously the point of using the Transition component with the RouterView is to have a transition, so I think for most use cases the duration would be much higher than these.
I hope this helps, happy to provide any other information.