Skip to content

Framework7 Navigation and Router — URL Routing, History, and Transitions

DodaTech Updated 2026-06-28 7 min read

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.

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

  1. Not using async for data loading - Synchronous routes block the UI while loading. Use async routes with Promise-based data loading for smooth navigation.

  2. 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.

  3. Creating circular navigation - Navigating to the same page repeatedly without using reloadCurrent creates a growing history stack. Use reloadCurrent for tabs or refreshes.

  4. Not defining a 404 route - Navigation to undefined routes causes errors. Always include a wildcard route at the end of your route array.

  5. Mixing hash and pushState navigation - Choose one URL Strategy. Hash routes (#/page/) work without server config. PushState (/page/) requires server URL rewriting.

Practice Questions

  1. How do you define a route with a URL parameter?
  2. What is the difference between navigate and back?
  3. How do you prevent navigation to a page with a route guard?
  4. What are the available page transition types?
  5. 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

What URL strategy should I use?

Use pushState for production (clean URLs). Use hash for simple apps or when server URL rewriting is not possible. Set url: 'pushState' in the Framework7 config.

How do I handle the Android back button?

Use app.on('backButton', function() { mainView.router.back(); }). Cordova plugins may also provide back button events that you can forward to the router.

Can I animate page transitions with custom CSS?

Yes. Override Framework7's CSS transition classes. The router adds classes like router-transition-forward and router-transition-backward during navigation.

How do I share data between routes?

Use global app variables (app.data = {}), Framework7 store, or props passed via navigate(). For complex state, use a state management library like F7 Store or external stores.

Does Framework7 support nested routes?

Routes are flat by design. For nested navigation, use nested views inside a page or use tab-based navigation within a single view.

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