5.7 KiB
5.7 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| KeepAlive Router Navigation Fresh vs Cached Problem | MEDIUM | When using KeepAlive with Vue Router, users may get cached pages when they expect fresh content | gotcha |
|
KeepAlive Router Navigation Fresh vs Cached Problem
Impact: MEDIUM - When using KeepAlive with Vue Router, navigation from menus or breadcrumbs may show cached (stale) content when users expect a fresh page. This creates confusing UX where the page appears "stuck" on old data.
Task Checklist
- Define clear rules for when to use cached vs fresh pages
- Use route keys strategically to control freshness
- Implement
onActivatedto refresh stale data - Consider navigation source when deciding cache behavior
The Problem
<!-- App.vue -->
<template>
<nav>
<router-link to="/products">Products</router-link>
</nav>
<router-view v-slot="{ Component }">
<KeepAlive>
<component :is="Component" />
</KeepAlive>
</router-view>
</template>
Scenario:
- User visits
/products?category=shoes- sees shoes - User navigates to
/products?category=hats- sees hats - User clicks "Products" nav link (to
/products) - Expected: Fresh products page or default category
- Actual: Still shows hats (cached state)!
Users clicking navigation expect a "fresh start" but get the cached state.
Solutions
Solution 1: Use Route Full Path as Key
<template>
<router-view v-slot="{ Component, route }">
<KeepAlive>
<!-- Different query params = different cache entry -->
<component :is="Component" :key="route.fullPath" />
</KeepAlive>
</router-view>
</template>
Tradeoff: Creates separate cache entry for each unique URL. May increase memory usage.
Solution 2: Refresh Data on Activation
<!-- Products.vue -->
<script setup>
import { ref, onActivated, watch } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const products = ref([]);
const lastParams = ref(null);
async function fetchProducts() {
const params = route.query;
products.value = await api.getProducts(params);
lastParams.value = JSON.stringify(params);
}
// Initial fetch
fetchProducts();
// Refresh when re-activated if params changed
onActivated(() => {
const currentParams = JSON.stringify(route.query);
if (currentParams !== lastParams.value) {
fetchProducts();
}
});
// Also watch for changes while component is active
watch(() => route.query, fetchProducts, { deep: true });
</script>
Solution 3: Navigation-Aware Cache Control
Different behavior based on how user navigated:
<script setup>
import { ref, onActivated } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
const route = useRoute();
const router = useRouter();
const shouldRefresh = ref(false);
// Mark for refresh when coming from nav link (no query params)
onBeforeRouteUpdate((to, from) => {
// If navigating to base path without params, user wants fresh view
if (Object.keys(to.query).length === 0 && Object.keys(from.query).length > 0) {
shouldRefresh.value = true;
}
});
onActivated(() => {
if (shouldRefresh.value) {
resetToDefaultState();
shouldRefresh.value = false;
}
});
function resetToDefaultState() {
// Reset filters, clear search, show default view
}
</script>
Solution 4: Don't Cache Route-Dependent Pages
<script setup>
// Don't cache pages where query params significantly change content
const noCacheRoutes = ['ProductSearch', 'SearchResults', 'FilteredList'];
</script>
<template>
<router-view v-slot="{ Component, route }">
<KeepAlive :exclude="noCacheRoutes">
<component :is="Component" />
</KeepAlive>
</router-view>
</template>
Solution 5: Use Route Meta for Fresh Navigation
// router.js
const routes = [
{
path: '/products',
component: Products,
meta: {
keepAlive: true,
refreshOnDirectNavigation: true,
},
},
];
<!-- App.vue -->
<script setup>
import { watch } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
const forceRefreshKey = ref(0);
// Watch for navigation to routes that want fresh state
watch(
() => route.fullPath,
(newPath, oldPath) => {
// Direct navigation to base path = user wants fresh
if (route.meta.refreshOnDirectNavigation && !route.query.length) {
forceRefreshKey.value++;
}
},
);
</script>
<template>
<router-view v-slot="{ Component }">
<KeepAlive>
<component :is="Component" :key="`${route.name}-${forceRefreshKey}`" />
</KeepAlive>
</router-view>
</template>
Best Practice: Be Explicit About Cache Behavior
Document your caching rules:
// cacheRules.js
export const CACHE_RULES = {
// Always cached - static content, user preferences
ALWAYS: ['Dashboard', 'Settings', 'Profile'],
// Never cached - dynamic search/filter results
NEVER: ['SearchResults', 'FilteredProducts'],
// Cached but refreshes on activation
STALE_WHILE_REVALIDATE: ['Notifications', 'Messages'],
};
Key Points
- User expectation mismatch - Nav links often imply "fresh" but get cached
- Use
fullPathkey carefully - Prevents reuse but increases memory - Implement
onActivatedrefresh - Check if data needs updating - Don't cache filter/search pages - These are highly query-dependent
- Document cache behavior - Make rules explicit for your team