Vue.js 47 🟢 Preserving Component State with (include, exclude, max)
Swapping one component for another destroys the first and creates the second. The destroyed component loses everything: the form the user was halfway through, the scroll position in a long list, the cursor in a text field, the fetched data that took a second to load. In a tabbed interface or a wizard, this is rarely what the user wants. The <KeepAlive> component is the built-in that changes the rule. It caches the inactive component instances and restores them when they become active again, so switching tabs does not reset the state.
Key point: <KeepAlive> caches component instances, not their DOM. The cached component is deactivated rather than unmounted, and its DOM is removed from the document. When the component is activated, its DOM is reinserted, and the state is exactly as it was. The cache has a default limit of 12 components unless :max is set.
Why KeepAlive exists
The state-loss problem. A form with ten fields, half-filled, is lost when the user clicks a different tab. They must start over. A long list loses its scroll position, forcing the user to scroll back. A text editor loses the cursor and the undo history. None of this is necessary if the component’s state is preserved.
The fetch problem. A component that fetches data on mount runs the fetch every time it is created. For a tab that shows a dashboard, switching back and forth triggers the same API call repeatedly. The cached component avoids the refetch because it was never unmounted.
The lifecycle problem. A component’s onMounted and onUnmounted hooks fire on every swap. Side effects that should run once run many times: an analytics event, a WebSocket subscription, a timer. The <KeepAlive> replaces these hooks with onActivated and onDeactivated, which fire only when the component becomes visible or hidden.
The performance problem. Creating a component is more expensive than reinserting a cached one. For a heavy component with a complex template, the difference is measurable. The cache trades memory for speed.
The memory problem. A cache that grows without bound is a leak. Every cached component holds its state, its DOM, and its references. The :max prop bounds the cache, and the least recently used component is evicted when the limit is reached.
The routing problem. A multi-page application keeps the previous page’s state when the user returns. A single-page application loses it. The <KeepAlive> with the router preserves the state across route changes, which is what users expect from a page.
The scroll problem. The scroll position of a long list is part of the user’s context. Losing it forces a re-scroll. The <KeepAlive> preserves it, and onActivated can restore it explicitly if the browser has reset it.
a. The basic usage
The <KeepAlive> wraps a dynamic component. The wrapped component is cached when it is swapped out and restored when it is swapped back in.
The simplest form
<template>
<KeepAlive>
<component :is="currentComponent" />
</KeepAlive>
</template>
The <KeepAlive> has no props. It caches every component that is rendered inside it, up to the default limit of 12 instances.
The lifecycle change
Inside a <KeepAlive>, the onMounted hook fires once when the component is created, and the onActivated hook fires every time the component becomes visible. The onUnmounted hook does not fire until the cache evicts the component or the parent is unmounted.
<script setup>
import { onMounted, onActivated, onDeactivated, onUnmounted } from 'vue';
onMounted(() => {
console.log('mounted'); // Fires once
});
onActivated(() => {
console.log('activated'); // Fires on every show
});
onDeactivated(() => {
console.log('deactivated'); // Fires on every hide
});
onUnmounted(() => {
console.log('unmounted'); // Fires on eviction
});
</script>
The three hooks have distinct roles: onMounted for one-time setup, onActivated for refresh, and onDeactivated for pausing.
The template ref
A ref on a cached component resolves only when the component is active. When the component is deactivated, the ref is null.
<KeepAlive>
<component :is="currentComponent" ref="childRef" />
</KeepAlive>
The childRef.value is the component instance when it is active and null when it is cached.
The <Transition> composition
The <KeepAlive> composes with <Transition>. The transition wraps the cached component and animates the enter and leave.
<Transition name="fade" mode="out-in">
<KeepAlive>
<component :is="currentComponent" :key="currentComponent.name" />
</KeepAlive>
</Transition>
The :key is required for the transition to fire when the component changes.
The <Suspense> composition
The <KeepAlive> composes with <Suspense>. An async component inside the cache is cached after its first resolution.
<Suspense>
<KeepAlive>
<component :is="asyncComponent" />
</KeepAlive>
</Suspense>
The nested <KeepAlive>
A <KeepAlive> cannot be nested inside another <KeepAlive> for the same component. The outer cache takes precedence, and the inner cache is ignored. The <KeepAlive> should be placed at the top of the tree.
b. The include and exclude props
The <include> and <exclude> props filter which components are cached. The values are comma-separated strings, arrays of strings, or regular expressions, and they match against the component’s name.
The include
The <include> prop lists the components to cache. A component not in the list is not cached.
<KeepAlive include="TabHome,TabProfile">
<component :is="currentComponent" />
</KeepAlive>
Only TabHome and TabProfile are cached. The other components are created and destroyed on each swap.
The array form
The prop accepts an array of strings.
<KeepAlive :include="['TabHome', 'TabProfile']">
<component :is="currentComponent" />
</KeepAlive>
The regex form
The prop accepts a regular expression.
<KeepAlive :include="/^Tab/">
<component :is="currentComponent" />
</KeepAlive>
The pattern matches every component whose name starts with Tab.
The exclude
The <exclude> prop lists the components not to cache. It is the inverse of <include>.
<KeepAlive exclude="TabSettings">
<component :is="currentComponent" />
</KeepAlive>
Every component is cached except TabSettings.
The include and exclude together
When both are present, <exclude> takes precedence.
<KeepAlive include="/^Tab/" exclude="TabSettings">
<component :is="currentComponent" />
</KeepAlive>
The TabSettings component matches both, and the exclude wins. It is not cached.
The component name requirement
The include and exclude match against the component’s name. A component defined with <script setup> uses its filename as the name. A component that needs a specific name can set it with a separate <script> block or defineOptions.
<script>
export default {
name: 'TabHome',
};
</script>
<script setup>
// ...
</script>
The name is required for the include and exclude to work. A component without a name is never matched and is cached by default when no filter is present.
The dynamic include
The include and exclude props are reactive. Changing them changes the filter on the next render.
<script setup>
import { ref } from 'vue';
const cacheTabs = ref(['TabHome', 'TabProfile']);
</script>
<template>
<KeepAlive :include="cacheTabs">
<component :is="currentComponent" />
</KeepAlive>
</template>
The filter is recomputed when cacheTabs changes.
The filter’s effect on existing caches
When a component is removed from the include list, its cached instance is not immediately evicted. The cache remains until the component is unmounted or the cache is otherwise cleared. The Vue documentation notes that the cache is not pruned reactively.
c. The max prop
The <max> prop limits the number of cached instances. When the limit is exceeded, the least recently used instance is evicted.
The basic usage
<KeepAlive :max="5">
<component :is="currentComponent" />
</KeepAlive>
The cache holds at most five components. The sixth swap evicts the least recently used one.
The LRU eviction
The eviction policy is least recently used. The component that has been inactive the longest is removed when the cache is full.
Cache capacity: 3
Access order: A, B, C
Cache: [A, B, C]
Access D:
Cache: [B, C, D] (A was evicted)
Access B:
Cache: [C, D, B] (B is now the most recent)
The LRU order is maintained by the <KeepAlive> internally.
The eviction lifecycle
An evicted component is unmounted. Its onUnmounted hook fires, and its state is lost.
<script setup>
import { onUnmounted } from 'vue';
onUnmounted(() => {
console.log('evicted'); // Fires when the cache evicts the component
});
</script>
The default limit
The default max is 12. A <KeepAlive> without a max prop holds up to 12 instances before evicting.
The combination with include
The :max and :include compose. The cache holds at most :max components, and only those matching :include are eligible.
<KeepAlive :include="['TabHome', 'TabProfile', 'TabReports']" :max="2">
<component :is="currentComponent" />
</KeepAlive>
Only three components match the include, and the cache holds at most two of them.
The unbounded cache
A <KeepAlive> without an include, exclude, or max caches every component it renders, up to the default of 12. For a dynamic list of components that grows without bound, the explicit :max is required.
Complete Example Session
<!-- ============================================
PART 1: THE BASIC KEEPALIVE
============================================ -->
<script setup>
import { shallowRef } from 'vue';
import TabHome from './TabHome.vue';
import TabSettings from './TabSettings.vue';
const current = shallowRef(TabHome);
</script>
<template>
<KeepAlive>
<component :is="current" />
</KeepAlive>
<button @click="current = TabHome">Home</button>
<button @click="current = TabSettings">Settings</button>
</template>
<!-- ============================================
PART 2: THE LIFECYCLE HOOKS
============================================ -->
<script setup>
import { onMounted, onActivated, onDeactivated, onUnmounted } from 'vue';
onMounted(() => console.log('mounted'));
onActivated(() => console.log('activated'));
onDeactivated(() => console.log('deactivated'));
onUnmounted(() => console.log('unmounted'));
</script>
<!-- ============================================
PART 3: THE INCLUDE FILTER
============================================ -->
<KeepAlive include="TabHome,TabProfile">
<component :is="current" />
</KeepAlive>
<!-- ============================================
PART 4: THE REGEX FILTER
============================================ -->
<KeepAlive :include="/^Tab/">
<component :is="current" />
</KeepAlive>
<!-- ============================================
PART 5: THE EXCLUDE FILTER
============================================ -->
<KeepAlive exclude="TabSettings">
<component :is="current" />
</KeepAlive>
<!-- ============================================
PART 6: THE MAX LIMIT
============================================ -->
<KeepAlive :max="5">
<component :is="current" />
</KeepAlive>
<!-- ============================================
PART 7: THE DYNAMIC FILTER
============================================ -->
<script setup>
import { ref, shallowRef } from 'vue';
const cacheTabs = ref(['TabHome', 'TabProfile']);
const current = shallowRef(TabHome);
</script>
<template>
<KeepAlive :include="cacheTabs">
<component :is="current" />
</KeepAlive>
</template>
<!-- ============================================
PART 8: THE TRANSITION COMPOSITION
============================================ -->
<Transition name="fade" mode="out-in">
<KeepAlive>
<component :is="current" :key="current.name" />
</KeepAlive>
</Transition>
<!-- ============================================
PART 9: THE NAMED COMPONENT
============================================ -->
<!-- TabHome.vue -->
<script>
export default { name: 'TabHome' };
</script>
<script setup>
// ...
</script>
<!-- ============================================
PART 10: THE COMPLETE PATTERN
============================================ -->
<!--
1. Wrap the dynamic component with <KeepAlive>.
2. Use onActivated and onDeactivated instead of onMounted and onUnmounted.
3. Use :include to list the components to cache.
4. Use :exclude to list the components not to cache.
5. Use :max to limit the cache size.
6. Name the components so the filters work.
7. Compose with <Transition> and <Suspense>.
8. The cache preserves the state and the scroll position.
-->
The ten parts showed the basic <KeepAlive>, the lifecycle hooks, the include filter, the regex filter, the exclude filter, the max limit, the dynamic filter, the transition composition, the named component, and the complete pattern.
Quick Reference
The KeepAlive Props
| Prop | Type | Purpose |
|---|---|---|
include | String, Array, RegExp | The components to cache |
exclude | String, Array, RegExp | The components not to cache |
max | Number | The maximum cache size |
The Lifecycle Hooks
| Hook | When |
|---|---|
onMounted | Once, on creation |
onActivated | Every time the component becomes visible |
onDeactivated | Every time the component is hidden |
onUnmounted | On eviction or parent unmount |
The Default Behavior
| Aspect | Default |
|---|---|
| Cache size | 12 |
| Eviction policy | Least recently used |
| Filter match | Component name |
The Name Sources
| Setup | Name |
|---|---|
<script setup> | Filename |
| Options API | name property |
defineOptions | name option |
The Filter Forms
| Form | Example |
|---|---|
| String | include="TabHome,TabProfile" |
| Array | :include="['TabHome', 'TabProfile']" |
| RegExp | :include="/^Tab/" |
Best Practices
✅ Do This:
<!-- Use onActivated for refresh logic. -->
onActivated(() => {
refreshData();
}); <!-- ✅ -->
<!-- Use onDeactivated for pause logic. -->
onDeactivated(() => {
clearInterval(timer);
}); <!-- ✅ -->
<!-- Use :include to cache only specific components. -->
<KeepAlive include="TabHome,TabProfile"> <!-- ✅ -->
<!-- Use :max to bound the cache. -->
<KeepAlive :max="5"> <!-- ✅ -->
<!-- Name the components for the filters. -->
<script>export default { name: 'TabHome' };</script> <!-- ✅ -->
<!-- Use :key with the transition. -->
<Transition mode="out-in">
<KeepAlive><component :is="current" :key="current.name" /></KeepAlive>
</Transition> <!-- ✅ -->
❌ Don’t Do This:
<!-- Don't rely on onMounted for refresh logic. -->
onMounted(() => {
refreshData(); <!-- ❌ Fires only once -->
}); <!-- ❌ -->
<!-- Don't forget the include when the list is dynamic. -->
<KeepAlive> <!-- ❌ Unbounded cache -->
<component :is="current" />
</KeepAlive> <!-- ❌ -->
<!-- Don't nest KeepAlive for the same component. -->
<KeepAlive><KeepAlive><component :is="current" /></KeepAlive></KeepAlive> <!-- ❌ -->
<!-- Don't expect the ref to work when cached. -->
childRef.value.method(); <!-- ❌ null when deactivated -->
<!-- Don't forget to name the components. -->
<KeepAlive include="TabHome"> <!-- ❌ The component has no name -->
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| State not preserved | No <KeepAlive> | Wrap the component |
| Refresh not running | Using onMounted | Use onActivated |
| Filter not matching | Component has no name | Name the component |
| Cache grows unbounded | No :max | Set the limit |
| Transition does not fire | Missing :key | Add the key |
| Ref is null | Component is deactivated | Check before use |
| Filter not reactive | Static include | Use a ref or computed |
Real-World Examples
1. Basic KeepAlive
<KeepAlive><component :is="current" /></KeepAlive>
2. Lifecycle Hooks
onActivated(() => refreshData());
onDeactivated(() => pauseTimer());
3. Include Filter
<KeepAlive include="TabHome,TabProfile">
4. Exclude Filter
<KeepAlive exclude="TabSettings">
5. Max Limit
<KeepAlive :max="5">
6. Dynamic Filter
<KeepAlive :include="cacheTabs">
7. Transition Composition
<Transition mode="out-in">
<KeepAlive><component :is="current" :key="current.name" /></KeepAlive>
</Transition>
8. Named Component
<script>export default { name: 'TabHome' };</script>
9. The Regex Filter
<KeepAlive :include="/^Tab/">
10. The Complete Pattern
<Transition mode="out-in">
<KeepAlive :include="cacheTabs" :max="5">
<component :is="current" :key="current.name" />
</KeepAlive>
</Transition>
Visual
The Cache Lifecycle
┌────────────────────────────────────────────────────────────┐
│ THE CACHE LIFECYCLE │
│ │
│ WITHOUT KEEPALIVE │
│ ┌──────────────────────────────────────────────────┐ │
│ │ TabHome → TabSettings │ │
│ │ onUnmounted(TabHome) │ │
│ │ onMounted(TabSettings) │ │
│ │ TabHome's state is lost. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ WITH KEEPALIVE │
│ ┌──────────────────────────────────────────────────┐ │
│ │ TabHome → TabSettings │ │
│ │ onDeactivated(TabHome) │ │
│ │ onMounted(TabSettings) │ │
│ │ onActivated(TabSettings) │ │
│ │ TabHome's state is preserved. │ │
│ │ │ │
│ │ TabSettings → TabHome │ │
│ │ onDeactivated(TabSettings) │ │
│ │ onActivated(TabHome) │ │
│ │ TabHome's state is restored. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Lifecycle Hooks
┌────────────────────────────────────────────────────────────┐
│ THE LIFECYCLE HOOKS │
│ │
│ OUTSIDE KEEPALIVE │
│ ┌──────────────────────────────────────────────────┐ │
│ │ onMounted → once, on creation │ │
│ │ onUnmounted → once, on destruction │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ INSIDE KEEPALIVE │
│ ┌──────────────────────────────────────────────────┐ │
│ │ onMounted → once, on creation │ │
│ │ onActivated → every time the component is shown │ │
│ │ onDeactivated→ every time the component is hidden│ │
│ │ onUnmounted → once, on eviction │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ Use onActivated for refresh, onDeactivated for pause. │
└────────────────────────────────────────────────────────────┘
The LRU Eviction
┌────────────────────────────────────────────────────────────┐
│ THE LRU EVICTION │
│ │
│ Cache capacity: 3 │
│ │
│ Show A → Cache: [A] │
│ Show B → Cache: [A, B] │
│ Show C → Cache: [A, B, C] │
│ Show A → Cache: [B, C, A] (A is now most recent) │
│ Show D → Cache: [C, A, D] (B was least recent, evicted) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ The least recently used component is evicted │ │
│ │ when the cache is full. │ │
│ │ The evicted component is unmounted. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Filter Behavior
┌────────────────────────────────────────────────────────────┐
│ THE FILTER BEHAVIOR │
│ │
│ include="TabHome,TabProfile" │
│ ┌──────────────────────────────────────────────────┐ │
│ │ TabHome → cached │ │
│ │ TabProfile → cached │ │
│ │ TabSettings→ NOT cached (created each time) │ │
│ │ TabReports → NOT cached │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ exclude="TabSettings" │
│ ┌──────────────────────────────────────────────────┐ │
│ │ TabHome → cached │ │
│ │ TabProfile → cached │ │
│ │ TabSettings→ NOT cached │ │
│ │ TabReports → cached │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ include="/^Tab/" exclude="TabSettings" │
│ ┌──────────────────────────────────────────────────┐ │
│ │ TabHome → cached │ │
│ │ TabProfile → cached │ │
│ │ TabSettings→ NOT cached (exclude wins) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
<KeepAlive> | Caches inactive component instances |
include | The components to cache |
exclude | The components not to cache |
max | The maximum cache size |
onActivated | Fires when the component becomes visible |
onDeactivated | Fires when the component is hidden |
| Default cache size | 12 |
| Eviction policy | Least recently used |
| Filter match | Component name |
Key takeaways:
<KeepAlive>caches the inactive components instead of destroying them. The cached component is deactivated, and its state is preserved. When the component becomes active again, it is restored with the state exactly as it was.- The lifecycle hooks change inside a
<KeepAlive>. TheonMountedhook fires once, and theonActivatedhook fires every time the component becomes visible. TheonDeactivatedhook fires every time it is hidden, and theonUnmountedhook fires on eviction. - The
includeandexcludeprops filter the cache. The filters match against the component’s name, which comes from the filename in<script setup>or thenameproperty in the Options API. Theexcludetakes precedence when both match. - The
maxprop bounds the cache. The default is 12. The least recently used component is evicted when the limit is reached, and the evicted component is unmounted. - The
:keyis required with<Transition>. Without the key, Vue reuses the DOM element, and the transition does not fire. The key forces a new element when the component changes. - The refs are null when the component is cached. A template ref on a cached component resolves only when the component is active. The ref is
nullwhen the component is deactivated. - The
onActivatedhook is for refresh logic. TheonMountedhook fires only once, so data that changes while the component is cached must be refreshed in theonActivatedhook. - The
<KeepAlive>cannot be nested. A<KeepAlive>inside another<KeepAlive>for the same component is ignored, and the outer cache takes precedence.
Remember: The <KeepAlive> component preserves the state of the components it caches. The inactive components are deactivated instead of unmounted, and their DOM is removed from the document. When the component becomes active again, the DOM is reinserted and the state is exactly as it was. The include and exclude props filter which components are cached, and the max prop bounds the cache size. The onActivated and onDeactivated hooks replace the onMounted and onUnmounted hooks for the refresh and pause logic. The result is a tabbed interface, a wizard, or a router with the state preserved across the swaps, and a user experience that matches the expectation of a page that does not reset.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!