963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
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