Vue 3 KeepAlive: caché de rutas, preservación de estado y control de memoria
<KeepAlive> es el componente integrado de Vue para cachear instancias inactivas. Sirve cuando el usuario sale de una página y vuelve esperando conservar filtros, formularios, scroll y estado local de UI.
También es fácil usarlo mal. Si cacheas todas las rutas, crece la memoria. Si nunca refrescas datos, los usuarios ven información antigua. Si nombres de componentes y keys no están controlados, el caché puede comportarse distinto a lo esperado.
Referencias:
Cuándo vale la pena usar KeepAlive
Úsalo en páginas donde preservar estado local mejora el flujo:
- Listas con filtros, búsqueda, tabs y scroll.
- Formularios multi-paso donde el usuario puede visitar otra página y volver.
- Dashboards con renderizado local costoso.
- Tabs de administración que no deben reiniciarse al cambiar.
Evítalo cuando:
- La página es barata de recrear.
- Debe mostrar datos frescos siempre.
- Contiene datos sensibles que deben desaparecer al salir.
- La componente tiene timers, suscripciones u objetos grandes difíciles de pausar.
El default debe ser caché selectivo, no cachear todas las rutas.
Configuración básica por ruta
Vue Router expone el componente renderizado mediante el slot de RouterView. Es el lugar limpio para envolver páginas seleccionadas con KeepAlive.
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive :include="cachedComponentNames" :max="8">
<component
v-if="route.meta.keepAlive"
:is="Component"
:key="route.meta.cacheKey || route.name"
/>
</KeepAlive>
<component
v-if="!route.meta.keepAlive"
:is="Component"
:key="route.fullPath"
/>
</RouterView>
</template>
<script setup lang="ts">
const cachedComponentNames = ["UserList", "OrderList", "ReportDashboard"]
</script>
Los metadatos de ruta mantienen la decisión cerca de la ruta.
// router.ts
import type { RouteRecordRaw } from "vue-router"
export const routes: RouteRecordRaw[] = [
{
path: "/users",
name: "Users",
component: () => import("./views/UserList.vue"),
meta: {
keepAlive: true,
cacheKey: "users-list"
}
},
{
path: "/users/:id",
name: "UserDetail",
component: () => import("./views/UserDetail.vue"),
meta: {
keepAlive: false
}
}
]
Mantén nombres de componentes estables si usas include o exclude, porque Vue hace match por nombre.
<!-- UserList.vue -->
<script setup lang="ts">
defineOptions({
name: "UserList"
})
</script>
Refrescar datos sin reiniciar UI
El bug más común son datos viejos. Una página cacheada puede conservar resultados antiguos después de volver.
Usa onActivated para decidir si se debe refrescar. No recargues todo cada vez o perderás parte del beneficio.
<script setup lang="ts">
import { onActivated, onDeactivated, ref } from "vue"
const users = ref<User[]>([])
const loading = ref(false)
const lastLoadedAt = ref(0)
const STALE_AFTER_MS = 60_000
async function loadUsers() {
loading.value = true
try {
users.value = await fetchUsers()
lastLoadedAt.value = Date.now()
} finally {
loading.value = false
}
}
onActivated(() => {
const isStale = Date.now() - lastLoadedAt.value > STALE_AFTER_MS
if (!users.value.length || isStale) {
void loadUsers()
}
})
onDeactivated(() => {
// Pause work that should not continue while the page is hidden.
})
</script>
Así conservas estado local y aun así actualizas datos cuando envejecen.
Preservar scroll
KeepAlive conserva la instancia, pero layout, transiciones o contenedores pueden afectar el scroll.
Para un contenedor con scroll:
<template>
<main ref="scrollEl" class="page-scroll">
<!-- list content -->
</main>
</template>
<script setup lang="ts">
import { nextTick, onActivated, onDeactivated, ref } from "vue"
const scrollEl = ref<HTMLElement | null>(null)
let savedTop = 0
onDeactivated(() => {
savedTop = scrollEl.value?.scrollTop ?? 0
})
onActivated(async () => {
await nextTick()
if (scrollEl.value) {
scrollEl.value.scrollTop = savedTop
}
})
</script>
Prueba navegación directa, botón atrás y cambios programáticos de ruta.
Controlar tamaño de caché
max limita cuántas instancias conserva KeepAlive. Al superar el límite, Vue elimina entradas antiguas.
<KeepAlive :max="8">
<component :is="Component" />
</KeepAlive>
Elige el límite según flujos reales. No lo subas demasiado para ocultar problemas de estado.
Evitar fugas de memoria
Las componentes cacheadas se desactivan, no se desmontan. Parte de la limpieza va en onDeactivated, y la limpieza final en onUnmounted.
Pausa o elimina:
- Intervals y timeouts.
- Listeners WebSocket o SSE que no deben correr en background.
- ResizeObserver e IntersectionObserver.
- Listeners globales.
- Arrays, maps o blobs temporales grandes.
import { onActivated, onDeactivated, onUnmounted } from "vue"
let timer: number | undefined
function startPolling() {
timer = window.setInterval(refreshStatus, 15_000)
}
function stopPolling() {
if (timer) {
window.clearInterval(timer)
timer = undefined
}
}
onActivated(startPolling)
onDeactivated(stopPolling)
onUnmounted(stopPolling)
Si la memoria crece tras navegar varias veces, perfila con devtools en vez de adivinar.
Cache keys y rutas dinámicas
Las rutas dinámicas necesitan keys cuidadosas. Si todas las páginas de detalle usan el mismo nombre y key, pueden compartir una instancia sin querer.
<!-- Share one cached instance for the user list -->
<component :is="Component" :key="route.name" />
<!-- Separate cache entry for each route parameter -->
<component :is="Component" :key="route.fullPath" />
<!-- Controlled custom key -->
<component :is="Component" :key="route.meta.cacheKey || route.fullPath" />
No uses fullPath sin pensar en listas con muchas combinaciones de query. Puede crear demasiadas entradas.
Reglas prácticas
Usa KeepAlive cuando la página tiene estado local valioso. No lo uses como interruptor genérico de rendimiento.
Buenos candidatos:
- Listas de búsqueda.
- Borradores de formularios.
- Dashboards con setup costoso.
- Tabs donde se espera cambio instantáneo.
Malos candidatos:
- Login y autenticación.
- Pagos o páginas sensibles.
- Páginas que deben recargar siempre.
- Detalles con muchos IDs sin límite claro.
Checklist final
Antes de publicar:
- Solo rutas seleccionadas usan KeepAlive.
- Componentes cacheados tienen nombres estables.
- Rutas dinámicas tienen keys intencionales.
maxes razonable.- Datos se refrescan con
onActivatedcuando hace falta. - Timers, observers y listeners pausan al desactivar.
- Páginas sensibles no se cachean.
- Botón atrás, navegación directa y tabs están probados.
KeepAlive es confiable cuando se trata como preservación de estado, no como magia automática de rendimiento.
Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →