Files
agent-skills/skills/vue-best-practices/reference/dynamic-components-with-keepalive.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

5.5 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Use KeepAlive to Preserve Dynamic Component State MEDIUM Dynamic component switching destroys and recreates components, losing all internal state unless wrapped in KeepAlive best-practice
vue3
dynamic-components
keepalive
component-is
state-preservation
performance

Use KeepAlive to Preserve Dynamic Component State

Impact: MEDIUM - When switching between components using <component :is="...">, Vue destroys the old component and creates a new one. All internal state (form inputs, scroll position, fetched data) is lost. Wrapping dynamic components in <KeepAlive> caches them and preserves their state.

Task Checklist

  • Wrap <component :is> with <KeepAlive> when state preservation is needed
  • Use include and exclude to control which components are cached
  • Use max to limit cache size and prevent memory issues
  • Implement onActivated/onDeactivated hooks for cache-aware logic
  • Consider NOT using KeepAlive when fresh state is desired

The Problem: State Loss

<script setup>
import { ref, shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'

const currentTab = shallowRef(TabA)
</script>

<template>
  <button @click="currentTab = TabA">Tab A</button>
  <button @click="currentTab = TabB">Tab B</button>

  <!-- State is lost when switching tabs! -->
  <component :is="currentTab" />
</template>

If TabA has a form with user input, switching to TabB and back resets all input.

Solution: KeepAlive

<template>
  <button @click="currentTab = TabA">Tab A</button>
  <button @click="currentTab = TabB">Tab B</button>

  <!-- State is preserved when switching -->
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>
</template>

Now TabA's state persists even when TabB is displayed.

Controlling What Gets Cached

Include/Exclude by Name

Only cache specific components:

<template>
  <!-- Only cache components named 'TabA' or 'TabB' -->
  <KeepAlive include="TabA,TabB">
    <component :is="currentTab" />
  </KeepAlive>

  <!-- Cache all except 'HeavyComponent' -->
  <KeepAlive exclude="HeavyComponent">
    <component :is="currentTab" />
  </KeepAlive>

  <!-- Using regex -->
  <KeepAlive :include="/^Tab/">
    <component :is="currentTab" />
  </KeepAlive>

  <!-- Using array -->
  <KeepAlive :include="['TabA', 'TabB', 'Settings']">
    <component :is="currentTab" />
  </KeepAlive>
</template>

Important: Components must have a name option to be matched:

<!-- TabA.vue -->
<script>
export default {
  name: 'TabA' // Required for include/exclude matching
}
</script>

<script setup>
// ...composition API code
</script>

Or in Vue 3.3+ with <script setup>:

<script setup>
defineOptions({
  name: 'TabA'
})
</script>

Limit Cache Size

Prevent memory issues with many cached components:

<template>
  <!-- Only keep last 5 components in cache -->
  <KeepAlive :max="5">
    <component :is="currentTab" />
  </KeepAlive>
</template>

When cache exceeds max, the least recently accessed component is destroyed.

Lifecycle Hooks: onActivated and onDeactivated

Cached components need special lifecycle hooks:

<!-- TabA.vue -->
<script setup>
import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'

// Only called on first mount, NOT when switching back
onMounted(() => {
  console.log('TabA mounted (once)')
})

// Only called when truly destroyed, NOT when switching away
onUnmounted(() => {
  console.log('TabA unmounted')
})

// Called EVERY time component becomes visible
onActivated(() => {
  console.log('TabA activated')
  // Refresh data, resume timers, etc.
  fetchLatestData()
})

// Called EVERY time component is hidden (but kept in cache)
onDeactivated(() => {
  console.log('TabA deactivated')
  // Pause timers, save draft, etc.
  pauseAutoRefresh()
})
</script>

Common use cases for activation hooks:

  • Refresh stale data when returning to a tab
  • Resume/pause video or audio playback
  • Reconnect/disconnect WebSocket connections
  • Save/restore scroll position
  • Track analytics for tab views

KeepAlive with Vue Router

For route-based caching:

<!-- App.vue -->
<template>
  <router-view v-slot="{ Component }">
    <KeepAlive include="Dashboard,Settings">
      <component :is="Component" />
    </KeepAlive>
  </router-view>
</template>

With transition:

<template>
  <router-view v-slot="{ Component }">
    <Transition name="fade" mode="out-in">
      <KeepAlive>
        <component :is="Component" :key="$route.fullPath" />
      </KeepAlive>
    </Transition>
  </router-view>
</template>

When NOT to Use KeepAlive

Don't cache when:

<!-- Fresh data needed each time -->
<template>
  <!-- NO KeepAlive - want fresh search results each visit -->
  <component :is="currentView" />
</template>
  • Form should reset between visits
  • Data must be fresh (real-time dashboards)
  • Component has significant memory footprint
  • Security-sensitive data should be cleared

Performance Considerations

<script setup>
import { shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'

// Use shallowRef for component references
// Regular ref would deeply track the component object unnecessarily
const currentTab = shallowRef(TabA)
</script>

Reference