Taming Framework7's Page Stack: Caching, Guards, and the Memory Trade-Off
Framework7's router balances SPA navigation speed and memory through per-route caching and guards. Learn how to configure the page stack, prefetch data with beforeEnter, and avoid memory bloat on low-end devices.
16 Sept 2026, 23:43 UTC

The back-button tax
In a single-page application (SPA), the browser does not reload the document when the user navigates. Framework7 keeps a page stack—an ordered history of views—so the hardware back button and in-app back links behave like a native app. The friction appears when that stack grows: every page you visit either stays in memory or gets rebuilt. Rebuilding feels sluggish; retaining everything eventually crashes low-end devices.
The useful takeaway is that you do not need to choose between speed and stability globally. Framework7's router lets you decide, per route, whether a page should be cached and when data should load.
How the router manages the stack
Framework7's navigation controller is driven by a declarative route table. Each entry maps a URL path to a component (or an external HTML file). When the user navigates, the router pushes a new page onto the stack, animates the transition, and updates the browser history so deep links keep working.
Two mechanisms control resource use. First, route options such as cache: true tell the router to keep the page's DOM node—the browser's in-memory representation of the page—after navigating away. Second, navigation guards, which are callbacks like beforeEnter and beforeLeave, let you pause a transition while data loads or cleanup runs.
Worked example: list to detail
Consider a two-level app: a list screen and a detail screen. The list is lightweight and visited often; the detail screen fetches a record by ID.
Define the routes in your routes.js (or equivalent route array):
// routes.js
import ListPage from './pages/list.f7';
import DetailPage from './pages/detail.f7';
export default [
{
path: '/',
component: ListPage,
},
{
path: '/detail/:id/',
component: DetailPage,
options: {
cache: true,
},
beforeEnter: (to, from, resolve, reject) => {
const id = to.params.id;
fetch(`/api/items/${id}`)
.then((res) => {
if (!res.ok) throw new Error('Not found');
return res.json();
})
.then((data) => resolve(data))
.catch(() => reject());
},
},
];
In the list page, links use standard anchors so the router intercepts them:
<f7-list>
<f7-list-item link="/detail/42/" title="Item 42"></f7-list-item>
</f7-list>
When the user taps the link, the router starts the transition, runs beforeEnter, and waits for resolve() before the detail page becomes active. Because cache is true, the detail page stays in the DOM after the user navigates back to the list. Tapping another item pushes a new instance; returning to a previously visited ID reuses the cached node.
The memory-versus-UX trade-off
Caching improves perceived performance: the back transition is instant because the DOM is already built. The cost is memory. Each cached page retains its DOM subtree, event listeners, and any JavaScript references held by its component. On a low-RAM device, caching every route can lead to jank or a tab crash.
A practical middle ground is to cache pages that are cheap to rebuild but expensive to re-fetch—master-detail views, dashboards, and search results. Disable cache for heavy, rarely revisited screens such as image galleries or long forms. You can also use beforeLeave to cancel pending network requests or pause video playback before a page is cached.
One limitation is that a cached page keeps the data it resolved. If the underlying record changes, the cached view will show stale content until you invalidate it—either by disabling cache for that route or by refreshing in a lifecycle hook.
Checking the result
To verify behavior, run your dev server from the project root. If you generated the app with the Framework7 CLI, the command is typically npx framework7 serve or an npm script such as npm run serve—check your package.json scripts for the exact name.
Open browser devtools and navigate directly to a deep link such as http://localhost:8080/detail/42/. The router should reconstruct the stack, load the list page underneath, and display the detail page on top. In the Elements panel, confirm that navigating away leaves the detail page node in the DOM when cache is enabled. Take a heap snapshot before and after visiting several detail pages to observe the retained memory.
If a deep link lands on the root page instead of the expected view, check that the route path exactly matches the URL, including trailing slashes. A mismatch causes the router to fall back to the first matching route or the index.
Actionable next steps
- Enable
cache: trueonly on routes where users commonly navigate back and forth. - Use
beforeEnterto prefetch data andbeforeLeaveto abort requests or release resources. - Test deep links for every nested route to ensure the stack rebuilds correctly.
- Profile memory in devtools on a low-end device profile; disable cache for any page that causes sustained growth.
Treat the page stack as a budget, not a default. Selective caching plus guards gives you native-feeling navigation without an unbounded memory footprint.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.