Framework7 Router vs Vue Router/React Router: Picking One Navigation Layer per View
Framework7-Vue and Framework7-React support both the built-in router and Vue Router/React Router — but only one per View. A decision guide with trade-offs, lazy-loaded route config, and a device-level validation plan.
21 Mar 2026, 21:28 UTC

If you build a Framework7-Vue or Framework7-React app, you face a fork early: let Framework7's own router drive navigation, or plug in the router you already know (Vue Router or React Router). Both paths are supported, but they are not interchangeable inside the same screen. The short version: use Framework7 Router when native-feel mobile UX is the priority; use the framework-native router when you need SSR, centralized guards, or existing ecosystem tooling. Never run both against the same View.
The constraint that decides everything
Framework7 organizes navigation around Views. A View owns a history stack, renders page transitions (iOS slide, Android material), and drives the swipe-back gesture. Framework7 Router is built into that View: it knows when a page is animating in, when to fire pageBeforeIn / pageAfterOut lifecycle hooks, and how to coordinate components like Smart Select, Photo Browser, and Login Screen that open and close programmatically.
An external router knows none of this. If Vue Router changes the URL while Framework7's View is mid-transition, you get doubled history entries, stuck pages, or a swipe-back that pops the wrong screen. The rule that follows from this: one router per View. A hybrid where Vue Router handles "some routes" and Framework7 handles others inside the same View is the single most common cause of broken reloads and gesture glitches in these integrations.
Comparing the two options
| Criterion | Framework7 Router | Vue Router / React Router |
|---|---|---|
| Page transitions & swipe-back | Automatic, zero config | Disabled unless manually synced to the View |
| Framework7 components (Smart Select, modals) | Work out of the box | May misbehave without router context |
| Route guards | Per-page hooks (beforeEnter, beforeLeave) | Centralized, global guard patterns |
| SSR (Nuxt, Next.js) | Client-side only | Natural fit |
| Lazy loading | import() in route definition, code-split by bundler | Router's own lazy-loading mechanism |
| Bundle cost | ~3 KB gzipped (included) | ~10–15 KB gzipped additional |
| Team familiarity / devtools | Framework7-specific API | Familiar API, mature devtools |
Size figures are approximate and vary by version and bundler; measure your own build rather than budgeting from this table.
When each option wins
Choose Framework7 Router when the app is mobile-first and Framework7-centric. If your screens are mostly Framework7 pages, you rely on swipe-back, and you use components that open over pages (pickers, photo browsers, login screens), the built-in router removes an entire category of integration bugs. Deep linking and browser refresh behave correctly because one router fully owns history.
Choose Vue Router / React Router when navigation logic must live outside Framework7: server-side rendering, sharing route definitions with a web sibling app, complex global guard chains (auth, feature flags, analytics), or a team that already has router expertise and tests built around it. Accept the cost: you must render Framework7 pages as plain components inside your own route components, and you give up automatic transitions unless you wire the external router into a Framework7 View's router instance — which effectively means adopting Framework7 Router's model anyway.
Concrete implementation: Framework7 Router with lazy loading
In a Framework7-Vue project (scaffold with npm create framework7@latest), routes are plain objects passed to the app. Lazy loading uses a standard dynamic import, which Vite or webpack code-splits automatically:
// src/routes.js — runs at app bootstrap, no special permissions needed
const routes = [
{
path: '/',
component: () => import('./pages/home.vue'),
},
{
path: '/settings/',
component: () => import('./pages/settings.vue'),
beforeEnter({ resolve, reject }) {
// Per-page guard; call resolve() to continue or reject() to cancel
if (window.localStorage.getItem('onboarded')) resolve();
else reject();
},
},
];
export default routes;
// src/components/app.vue (template excerpt)
<f7-app :routes="routes">
<f7-view main url="/" ios-swipe-back />
</f7-app>
Two things to check here. First, the View has url="/" so the router controls it from the first paint — a View without an initial URL is a common source of a blank first screen. Second, guards in Framework7 Router are per-page; if you find yourself duplicating the same auth check across many routes, that repetition is a signal the external-router column of the table fits your app better.
Validating the decision before committing
Because the trade-offs are behavioral, validate on a real device, not just desktop Chrome:
- Build two minimal branches of the same three-screen app, one per router. Keep everything else identical.
- On iOS Safari and Android Chrome, push two pages deep, then swipe-back (iOS) or use the hardware back button (Android). Confirm the transition animates and the history stack pops once — not twice, not zero.
- Place a Smart Select on a page, open it, and trigger a route change programmatically. It should dismiss cleanly; with an unsynced external router it often stays orphaned.
- Refresh the browser on a deep route in both branches. The externally-routed branch only survives this if the external router fully owns history.
- Run
npm run buildand inspect chunk output (e.g., with a bundle analyzer) to confirm lazy routes split as expected and to record the real size delta. - If SSR is a requirement, attempt a Nuxt 3 or Next.js render of one page. Expect the Framework7 Router variant to fail hydration; treat that as confirmation, not a bug to fix.
Limitations and review notes
This guidance reflects Framework7 v8-era behavior; integration details and documentation emphasis shift between releases, so check the current Framework7 Vue/React integration docs and migration guide before locking the decision. The bundle-size figures are rough planning numbers, not measurements from a specific version. If your app genuinely needs both SSR for marketing pages and native transitions for the app shell, the clean answer is usually two separate bundles or a framework-native router everywhere — not two routers in one View.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.