Vue 3 KeepAlive : cache de routes, préservation d'état et contrôle mémoire
<KeepAlive> est le composant intégré de Vue pour mettre en cache des instances inactives. Il est utile quand un utilisateur quitte une page puis revient en s'attendant à retrouver filtres, formulaire, scroll et état local.
KeepAlive est aussi facile à mal utiliser. Si toutes les routes sont cachées, la mémoire augmente. Si les données ne sont jamais rafraîchies, les utilisateurs voient des informations obsolètes. Si les noms de composants et keys ne sont pas contrôlés, le cache peut surprendre.
Références :
Quand KeepAlive vaut le coup
Utilisez KeepAlive pour les pages où l'état local améliore le workflow :
- Liste avec filtres, recherche, onglets et position de scroll.
- Formulaire multi-étapes où l'utilisateur peut consulter une autre page.
- Dashboard avec rendu local coûteux.
- Onglets admin qui ne doivent pas se réinitialiser.
Évitez KeepAlive lorsque :
- La page est peu coûteuse à recréer.
- Elle doit toujours montrer des données fraîches.
- Elle contient des données sensibles à effacer au départ.
- Le composant possède timers, subscriptions ou gros objets difficiles à suspendre.
Le défaut doit être un cache sélectif, pas toutes les routes.
Setup route-level
Vue Router expose le composant rendu via le slot RouterView. C'est l'endroit propre pour utiliser 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>
Les métadonnées de route gardent la décision près de la définition.
// 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
}
}
]
Gardez des noms de composants stables avec include ou exclude, car Vue fait la correspondance par nom.
<!-- UserList.vue -->
<script setup lang="ts">
defineOptions({
name: "UserList"
})
</script>
Rafraîchir sans perdre l'état UI
Le bug le plus courant est la donnée périmée. Une page cachée peut conserver une ancienne réponse API.
Utilisez onActivated pour décider si un rafraîchissement est nécessaire. Ne rechargez pas tout à chaque activation.
<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>
Cela conserve filtres et scroll tout en mettant à jour les données anciennes.
Préserver le scroll
KeepAlive conserve l'instance, mais le scroll peut être affecté par le layout, les transitions ou un conteneur.
<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>
Testez navigation directe, bouton retour et navigation programmatique.
Limiter le cache
La prop max limite le nombre d'instances conservées. Quand la limite est dépassée, Vue supprime d'anciennes entrées.
<KeepAlive :max="8">
<component :is="Component" />
</KeepAlive>
Choisissez la limite selon les workflows réels. Ne l'augmentez pas pour masquer des problèmes de state management.
Éviter les fuites mémoire
Les composants cachés sont désactivés, pas démontés. Une partie du nettoyage appartient à onDeactivated, le nettoyage final à onUnmounted.
À suspendre ou supprimer :
- Intervals et timeouts.
- Listeners WebSocket ou SSE inutiles en arrière-plan.
- ResizeObserver et IntersectionObserver.
- Listeners globaux.
- Gros tableaux, maps ou blobs temporaires.
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 mémoire augmente après plusieurs navigations, profilez avec les devtools.
Keys et routes dynamiques
Les routes dynamiques demandent des keys réfléchies. Plusieurs pages détail avec même nom et même key peuvent partager une instance sans intention.
<!-- 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" />
N'utilisez pas fullPath par défaut pour des listes avec beaucoup de combinaisons de query. Cela peut créer trop d'entrées.
Règles pratiques
Utilisez KeepAlive pour préserver un état local utile, pas comme interrupteur générique de performance.
Bons candidats :
- Listes de recherche.
- Brouillons de formulaire.
- Dashboards avec charts coûteux.
- Onglets qui doivent revenir instantanément.
Mauvais candidats :
- Connexion et authentification.
- Paiement ou pages sensibles.
- Pages qui doivent toujours recharger.
- Pages détail avec beaucoup d'IDs sans limite claire.
Checklist finale
Avant de publier :
- Seules les routes choisies utilisent KeepAlive.
- Les composants cachés ont des noms stables.
- Les routes dynamiques ont des keys intentionnelles.
maxest raisonnable.- Les données se rafraîchissent via
onActivatedsi besoin. - Timers, observers et listeners s'arrêtent à la désactivation.
- Les pages sensibles ne sont pas cachées.
- Retour navigateur, accès direct et onglets sont testés.
KeepAlive est fiable quand il sert à préserver l'état, pas comme magie automatique de performance.
Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →