Framework7 Router Caching: keepAlive vs. ignoreCache and the Memory/UX Trade-off
Framework7's router offers default, keepAlive, and ignoreCache caching per route. Here's how to choose, a worked three-route example, and the stale-state bugs to watch for.
24 Aug 2026, 18:12 UTC

You build a Framework7 app with a long product list. The user scrolls forty items deep, taps one, hits back — and the list reloads from the top. The fix is one route option, but picking the wrong option creates a different bug: a settings page that shows yesterday's data, or a checkout form that never resets. Framework7's router gives you three caching modes per route, and the decision is really about which pages own state and which must never keep it.
The three caching modes
Every route in Framework7 renders into a stack-based navigation model. What happens to the component when you navigate away depends on the route's cache configuration:
- Default — the component is destroyed when you leave the page and recreated when you return. Clean state every visit, at the cost of re-running data fetching and losing scroll position.
keepAlive: true— the component instance stays in memory after you leave. Lifecycle hooks likeonMountedfire once; returning to the page is instant because nothing is rebuilt. Form inputs, scroll position, and timers all survive.ignoreCache: true— forces a fresh component on every visit, even in situations where the router might otherwise reuse one. This is the right choice for auth-sensitive or security-relevant pages.
The trade-off is memory versus perceived speed. keepAlive makes back-navigation feel native — especially combined with iOS swipe-back, which is enabled by default on the iOS theme — but each kept-alive page holds its DOM and state until the app decides otherwise.
A worked example: list, detail, logout
A typical three-route setup shows the decision clearly. This assumes Framework7 v8+ with Vue, React, or Svelte; the route options are framework-agnostic.
// routes.js
export default [
{
path: '/products/',
component: ProductsPage,
keepAlive: true, // preserve scroll + loaded pages
},
{
path: '/products/:id/',
component: ProductDetailPage,
// default: fresh instance per product
},
{
path: '/logout/',
component: LogoutPage,
ignoreCache: true, // never reuse a stale instance
},
];The products list is the classic keepAlive candidate: expensive to rebuild, and users expect their scroll position back. The detail page uses the default mode — each product ID should render fresh, and keeping every visited detail page in memory would grow unbounded. The logout page gets ignoreCache so a cached instance can never skip its teardown logic.
To verify the behavior, add a log to each page's mount and unmount hooks, then navigate list → detail → back a few times:
// inside ProductsPage (Vue example)
onMounted(() => console.log('products mounted'));
onBeforeUnmount(() => console.log('products unmounted'));With keepAlive: true, "products mounted" prints once no matter how many round-trips you make. Remove the option and it prints on every return. You can also inspect app.router.history in devtools: it grows on forward navigation and shrinks on back, and each View or Tab keeps its own history array.
The stale-state trap
keepAlive preserves everything, including things you forgot about. A search box keeps its query. A setInterval polling timer keeps running while the user is three pages deep. A form half-filled yesterday is still half-filled today.
The fix is explicit reset logic in the page lifecycle events the router fires on re-entry, such as pageBeforeIn, or in a beforeEnter guard:
{
path: '/products/',
component: ProductsPage,
keepAlive: true,
beforeEnter({ app, resolve }) {
// refresh data without rebuilding the page
app.store.dispatch('refreshProducts');
resolve();
},
}One caution with guards: they run synchronously unless they return a Promise. If your guard is async and you forget to return the Promise, the router proceeds before your data loads and the user sees an empty page flash. Return the Promise, or use the resolve/reject callback style, and test by returning Promise.resolve(false) — navigation should cancel and the URL stay put.
Limitations worth knowing
- Scroll restoration is main-view only.
restoreScrollTopOnBackhandles the primary view; a custom scrollable container inside akeepAlivepage needs manual save/restore ofscrollTop. - Tabs have independent stacks. Each tab view keeps its own history, so Android hardware back from a tab's root closes the app rather than switching tabs. If you want back to switch tabs first, you need a custom back handler.
- Async route components split aggressively. Routes using dynamic
import()get their own chunk via webpack/Vite. Fine-grained splitting is good, but dozens of tiny chunks add round-trips on slow networks — group related routes into shared chunks where it makes sense. - Memory is real. Every
keepAlivepage holds its component tree. On low-end devices, keeping five heavy pages alive can hurt more than the rebuild you were avoiding.
A practical default policy
Start with the default mode everywhere. Promote a page to keepAlive only when users demonstrably navigate back to it and rebuilding is visibly slow — lists, dashboards, and tabs are the usual winners. Reserve ignoreCache for pages where stale state is a correctness or security problem, not just an inconvenience. Then verify with the mount/unmount logging trick above; it takes five minutes and tells you exactly which mode each route is actually running in, which is more reliable than reasoning about the router from the config alone.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.