Framework7 Navigation and Router — URL Routing, History, and Transitions
In this tutorial, you will learn about Framework7 Navigation and Router. We cover key concepts, practical examples, and best practices to help you master this topic.
Framework7 router handles URL-based navigation with push/pop history, animated page transitions, route parameters, query strings, route guards, and Lazy Loading for efficient page rendering.
What You'll Learn
- Route configuration with parameters
- Programmatic navigation methods
- Route guards and middleware
- Page transitions and animation
- Lazy loading routes
Why It Matters
Mobile app navigation requires smooth transitions, proper back button handling, and URL-based state management. Framework7 router manages the navigation stack, animates page entrances/exits, and supports deep linking.
Real-World Use
An e-commerce app where product list pages navigate to product details, the back button returns to the list without reloading, category filters use query parameters, and the login route has a guard that redirects unauthenticated users.
Router Architecture
flowchart LR
A[Router] --> B[Route Config]
B --> C[Route Path]
B --> D[Route Params]
B --> E[Route Guards]
A --> F[Navigate]
A --> G[Back]
A --> H[Refresh]
F --> I[Push Page]
F --> J[Transition Animation]
I --> K[History Stack]
style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
Route Configuration
var app = new Framework7({
root: '#app',
routes: [
// Static route
{
path: '/',
url: 'pages/home.html'
},
// Route with parameter
{
path: '/product/:id/',
component: 'pages/product.html'
},
// Route with query parameters
{
path: '/products/',
component: 'pages/products.html'
},
// Route with multiple parameters
{
path: '/category/:categoryId/product/:productId/',
component: 'pages/product-detail.html'
},
// Optional parameter
{
path: '/page/:pageNum?',
component: 'pages/page.html'
},
// Wildcard route
{
path: '(.*)',
component: 'pages/404.html'
}
]
});
// Accessing parameters
$$(document).on('page:init', '.page[data-name="product"]', function(e) {
var route = e.detail.page.route;
console.log('Product ID:', route.params.id);
console.log('Query:', route.query);
console.log('URL:', route.url);
});
Expected output: Routes match URL patterns and extract parameters. The router selects the first matching route and loads its page component.
Navigation Methods
var mainView = app.views.get('.view-main');
// Basic navigation
mainView.router.navigate('/products/');
mainView.router.navigate('/product/42/');
// Navigate with query string
mainView.router.navigate('/products/?category=electronics&sort=price');
// Navigate with custom props
mainView.router.navigate('/product/42/', {
props: {
fromList: true,
referrer: 'search'
}
});
// Reload current page
mainView.router.navigate('/products/', {
reloadCurrent: true,
reloadAll: false
});
// Clear history and navigate (like replace)
mainView.router.navigate('/login/', {
clearPreviousHistory: true
});
// Navigate without animation
mainView.router.navigate('/settings/', {
animate: false
});
// Go back
mainView.router.back();
mainView.router.back({ force: true });
// Go back to a specific page
mainView.router.back('/products/', {
force: true
});
Expected output: Navigation methods control how pages enter the history stack. reloadCurrent refreshes the same page. clearPreviousHistory removes all previous pages. back() pops the stack.
Route Guards
var app = new Framework7({
routes: [
{
path: '/settings/',
component: 'pages/settings.html',
// Before enter guard
beforeEnter: function(routeTo, routeFrom, resolve, reject) {
// Check authentication
if (!app.user.isLoggedIn) {
reject();
mainView.router.navigate('/login/');
} else {
resolve();
}
},
// Before leave guard
beforeLeave: function(routeTo, routeFrom, resolve, reject) {
if (app.formDirty) {
app.dialog.confirm('Unsaved changes. Leave anyway?', function() {
resolve();
}, function() {
reject();
});
} else {
resolve();
}
}
}
],
// Global route guards
on: {
routeChangeStart: function(router, routeTo, routeFrom) {
console.log('Route changing from', routeFrom, 'to', routeTo);
},
routeChangeEnd: function(router, routeTo, routeFrom) {
console.log('Route changed to', routeTo);
},
routeError: function(router, routeTo, routeFrom, error) {
console.error('Route error:', error);
}
}
});
Expected output: beforeEnter guards redirect unauthenticated users to the login page. beforeLeave guards warn about unsaved changes. Global route events track navigation lifecycle.
Page Transitions
// Framework7 supports several transition types
var mainView = app.views.get('.view-main');
// Default transitions (fade-slide)
mainView.router.navigate('/page1/');
// Override transition type
mainView.router.navigate('/page2/', {
transition: 'fade' // 'fade', 'flip', 'cover-v', 'cover-h', 'parallax'
});
// Android Material transition
// In Material theme, transitions use elevation and shadow effects automatically
// Transition events
$$(document).on('page:beforein', '.page', function(e) {
console.log('Page transitioning in');
});
$$(document).on('page:afterin', '.page', function(e) {
console.log('Page transition complete');
});
// Disable transitions globally
var app = new Framework7({
animate: false // Disable all transitions
});
// Custom transition duration
// CSS: .router-transition { transition-duration: 500ms; }
Expected output: Pages transition with smooth animations. The default iOS transition slides content while the navbar fades. Custom transitions like flip and parallax provide different visual effects.
Lazy Loading Routes
var app = new Framework7({
routes: [
// Dynamic component loading
{
path: '/heavy-page/',
async: function(routeTo, routeFrom, resolve, reject) {
// Lazy load the component
import('./pages/heavy-page.js').then(function(module) {
resolve({
component: module.default
});
}).catch(function(err) {
reject(err);
});
}
},
// Route with preload data
{
path: '/dashboard/',
async: function(routeTo, routeFrom, resolve, reject) {
// Fetch data and component simultaneously
Promise.all([
fetch('/api/dashboard').then(function(r) { return r.json(); }),
import('./pages/dashboard.js')
]).then(function(results) {
var data = results[0];
var component = results[1].default;
// Pass data to component
component.data = data;
resolve({ component: component });
});
}
}
]
});
Expected output: Heavy pages and components load only when the user navigates to them, reducing initial bundle size. The async function supports dynamic imports and data fetching.
Deep Linking
// Framework7 supports deep linking out of the box
// URL: myapp://product/42/reviews/?page=2
// Parse deep link on app init
var app = new Framework7({
on: {
init: function() {
// Parse URL parameters
var url = window.location.href;
var params = app.utils.parseUrlQuery(url);
if (params.productId) {
mainView.router.navigate('/product/' + params.productId + '/');
}
}
}
});
// Handle incoming deep links (Cordova)
document.addEventListener('deviceready', function() {
window.handleOpenURL = function(url) {
var path = url.replace(/^[a-z]+:\/\//, '').replace(/\/$/, '');
mainView.router.navigate('/' + path + '/');
};
});
Expected output: Deep links from push notifications or external URLs navigate directly to the correct page. The router parses the URL and loads the matching route.
Router Events
// Subscribe to router events
var router = mainView.router;
// Route change start
router.on('routeChangeStart', function(routeTo, routeFrom) {
console.log('Starting navigation to:', routeTo.path);
});
// Route change end
router.on('routeChangeEnd', function(routeTo, routeFrom) {
console.log('Navigation completed to:', routeTo.path);
// Update analytics
});
// Route change error
router.on('routeError', function(routeTo, routeFrom, error) {
console.error('Navigation failed:', error);
});
// History change
router.on('historyChanged', function(history) {
console.log('History stack:', history);
// Update UI based on history state
});
Expected output: Router events provide hooks for analytics, loading indicators, and UI updates based on navigation state.
Common Mistakes
Not using async for data loading - Synchronous routes block the UI while loading. Use async routes with Promise-based data loading for smooth navigation.
Forgetting to handle the back button - The hardware back button on Android does not automatically navigate back. Use app.on('backButton', function() { router.back(); }) to handle it.
Creating circular navigation - Navigating to the same page repeatedly without using reloadCurrent creates a growing history stack. Use reloadCurrent for tabs or refreshes.
Not defining a 404 route - Navigation to undefined routes causes errors. Always include a wildcard route at the end of your route array.
Mixing hash and pushState navigation - Choose one URL Strategy. Hash routes (#/page/) work without server config. PushState (/page/) requires server URL rewriting.
Practice Questions
- How do you define a route with a URL parameter?
- What is the difference between navigate and back?
- How do you prevent navigation to a page with a route guard?
- What are the available page transition types?
- How do you lazy load a route component?
Challenge: Build a multi-page app with: a login page (initial route), a protected dashboard page (with beforeEnter guard checking auth), a product list with category filter using query params, a product detail page with route parameter, a settings page with beforeLeave guard for unsaved changes, and a 404 catch-all route.
FAQ
Mini Project
Build a routed product catalog app with: a home page with category grid, a product list page with query parameters for category/sort/search, a product detail page with route parameter (product ID), a login route with beforeEnter guard, a cart route with persistent state using F7 Store, and a 404 page for invalid URLs.
What's Next
The router handles navigation. Learn how Framework7 Toolbar and Tabbar creates bottom navigation bars with icons, badges, and tab switching.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro