Routes

Define Routes

First of all, when we init the Framework7 app we should pass default routes using routes array parameter:

var app = new Framework7({
  routes: [
    {
      name: 'about',
      path: '/about/',
      url: './pages/about.html',
    },
    {
      name: 'news',
      path: '/news/',
      url: './pages/news.html',
      options: {
        animate: false,
      },
    },
    {
      name: 'users',
      path: '/users/',
      componentUrl: './pages/users.html',
      options: {
        props: {
          users: ['John Doe', 'Vladimir Kharlampidi', 'Timo Ernst'],
        },
      },
      on: {
        pageAfterIn: function test (e, page) {
          // do something after page gets into the view
        },
        pageInit: function (e, page) {
          // do something when page initialized
        },
      }
    },
    // Default route, match to all pages (e.g. 404 page)
    {
      path: '(.*)',
      url: './pages/404.html',
    },
  ],
});

Routes defined on app init are default routes, they will be available for any View/Router in the app.

If you have a multi-view/router app and you want to have some View/Router to have own strict routes and don't want default routes be available in this View, then you may specify the same routes parameter on View init:

var view1 = app.views.create('.view-1', {
  routes: [
    {
      path: '/users/',
      url: './pages/users.html',
    },
    {
      path: '/user/',
      url: './pages/user.html',
    },
  ],
});

If you have a multi-view/router app and you want to have some View/Router to have additional routes and don't want these additional routes are available in other Views, then you may specify the routesAdd parameter on View init:

// This view will support all global routes + own additional routes
var view2 = app.views.create('.view-2', {
  // These routes are only available in this view
  routesAdd: [
    {
      path: '/blog/',
      url: './pages/blog.html',
    },
    {
      path: '/post/',
      url: './pages/post.html',
    },
  ],
})

Route Properties

Ok, now we will see what each route property means:

ParameterTypeDescription
namestringRoute name, e.g. home
pathstringRoute path. Means this route will be loaded when we click link that match to this path, or can be loaded by this path using API
optionsobjectObject with additional route options (optional)
routesarrayArray with nested routes
viewNamestringView name where this route will be forced to load
Master Detail
masterboolean
function(app, router)
Enables this route as Master route. It can also be a method that receives app and router instances, in this method you should return true or false:
{
  url: '/some-page/',
  master(app) {
    if (app.device.desktop) return true;
    return false;
  }
}
detailRoutesarrayArray with detail routes
Lazy Modules
modulesarrayArray with Lazy Modules to load before route loading
Content Related Properties
The following route properties define how (from where/what) content should be loaded
contentstringCreates dynamic page from specified content string
urlstringLoad page content via Ajax.

Also supports dynamic route params from route path using {{paramName}} expression, e.g.

{
  path: '/users/:userId/posts/:postId',
  url: 'http://myapp.com/posts/{{userId}}/{{postId}}'
}
componentobjectLoad page from passed Framework7 Router Component
componentUrlstringload pages as a component via Ajax

Also supports dynamic route params from route path using {{paramName}} expression

asyncfunction(context)Do required asynchronous manipulation and the return required route content and options. As an argument it receives route callback context object.
asyncComponentfunction()

Method should return Promise resolved with Component or ES module with .default property containing Component.

It is mostly designed as a shorter version of async to dynamically import components. For example:

{
  path: '/some-page/',
  asyncComponent: () => import('./path/to/some-page.js'),
}
Routable Tabs
tabsarrayArray with tab routes
Routable Modals
actionsobjectAction Sheet route
popupobjectPopup route
loginScreenobjectLogin screen route
popoverobjectPopover route
sheetobjectSheet route
Routable Panels
panelobjectPanel route
Events
onobjectObject with event handlers
Alias & Redirect
aliasstring
array
Route alias, or array with route aliases. We need to specify here alias path
redirectstring
function(context)
Route redirect. We need to specify here redirect url (not path). If method then as an argument it receives route callback context object.
Before Enter/Leave
beforeEnterfunction(context)

array
Function (or array of functions) that will be executed before route load/enter. To proceed route loading resolve must be called. In case of array then every function in array must be resolved to proceed. If method then as an argument it receives route callback context object.
beforeLeavefunction(context)

array
Function (or array of functions) that will be executed before route unload/leave. To proceed navigation resolve must be called. In case of array then every function in array must be resolved to proceed. If method then as an argument it receives route callback context object.
keepAlive
keepAlivebooleanEnables so called keepAlive route. When enabled then once loaded page and its component (Vue, React or Router component) will be never destroyed. Instead, it will be detached from DOM and reused again when required.

Here is an example for most of the possible options:

routes: [
  // Load via Ajax
  {
    path: '/about/',
    url: './pages/about.html',
  },
  // Dynamic page from content
  {
    path: '/news/',
    content: `
      <div class="page">
        <div class="page-content">
          <div class="block">
            <p>This page created dynamically</p>
          </div>
        </div>
      </div>
    `,
  },
  // By page name (data-name="services") presented in DOM
  {
    path: '/services/',
    pageName: 'services',
  },
  // By page HTMLElement
  {
    path: '/contacts/',
    el: document.querySelector('.page[data-name="contacts"]'),
  },
  // By component
  {
    path: '/posts/',
    component: {
      // look below
    },
  },
  // By component url
  {
    path: '/post/:id/',
    componentUrl: './pages/component.html',
  },
  // Async
  {
    path: '/something/',
    async: function ({ app, to, resolve }) {
      // Requested route
      console.log(to);
      // Get external data and return page content
      fetch('http://some-endpoint/')
        .then((res) => res.json())
        .then(function (data) {
          resolve(
            // How and what to load
            {
              content: `<div class="page">${data.users}</div>`
            },
          );
        });
      }
  }
],

Route Path

As stated above, the route's path property means the path/url that will be displayed in browser window address bar (if browserHistory enabled) when the following route will be loaded either by api or clicking on a link with same path.

There is also support for dynamic paths. So if you have the following path in your route /blog/users/:userId/posts/:postId/ and click the link with the with the /blog/users/12/posts/25 href then on loaded page we access route.params object containing { userId: 12, postId: 25 }

Route path matching is handled by Path To Regexp library, so everything what is supported there is supported in Framework7 as well. For example, if you want to add default route which match all paths, we can use regular expression like:

// Default route, match to all pages (e.g. 404 page)
{
  path: '(.*)',
  url: './pages/404.html',
},

Route Options

Lets look at additional route options that can be passed in options property:

ParameterTypeDescription
animatebooleanwhether the page should be animated or not (overwrites default router settings)
historybooleanwhether the page should be saved in router history
browserHistorybooleanwhether the page should be saved in browser state. In case you are using browserHistory, then you can pass here false to prevent route getting in browser history
reloadCurrentbooleanreplace the current page with the new one from route, no animation in this case
reloadPreviousbooleanreplace the previous page in history with the new one from route
reloadAllbooleanload new page and remove all previous pages from history and DOM
clearPreviousHistorybooleanprevious pages history will be cleared after reloading/navigate to the specified route
ignoreCachebooleanif set to true then it will ignore if such URL in cache and reload it using XHR again
forcebooleanif set to true then it will ignore previous page in history and load specified one
propsobjectprops that will be passed as Vue/React page component props
transitionstringCustom page transition name
openInstringAllows to open page route as modal or panel. So it can be one of the following: popup, popover, loginScreen, sheet, panel

Route Callback Context

Format of the route context callback used in async, redirect, beforeEnter and beforeLeave route properties:

Properties
appLink to global app instance
toRequested Route
fromCurrently active Route
routerCurrent Router instance
resolveMethod to call to resolve/proceed routing
rejectMethod to call to prevent/reject routing
directionNavigation direction, can be forward or backward

Async Route

async route property is very powerful tool designed to return dynamic route properties. It is a function with the following arguments:

async(context)

resolve method of route callback has the following format:

resolve(parameters, options)

  • parameters object - object with resolved route content. Must contain one of url, content, component or componentUrl properties
  • options object - object with Route Options

reject callback function doesn't have arguments:

reject()

Note that until you call resolve or reject in async method, routing will be blocked!

For example:

routes = [
  {
    path: '/foo/',
    async({ resolve, reject }) {
      if (userIsLoggedIn) {
        resolve({ url: 'secured.html' })
      } else {
        resolve({ url: 'login.html' })
      }
    }
  }
]

Route Events

It is possible to add all page events inside of route for this page using on route property. For example:

var app = new Framework7({
  routes: [
    // ...
    {
      path: '/users/',
      url: './pages/users.html',
      on: {
        pageBeforeIn: function (event, page) {
          // do something before page gets into the view
        },
        pageAfterIn: function (event, page) {
          // do something after page gets into the view
        },
        pageInit: function (event, page) {
          // do something when page initialized
        },
        pageBeforeRemove: function (event, page) {
          // do something before page gets removed from DOM
        },
      }
    },
    // ...
  ],
});

Please note, that such route events are actually DOM events, so each such handler will accept event as a first argument with the event itself and page as the second argument with page data.

Also, context of such event handler (this) will point to related Router instance.

Nested Routes

It is possible to have nested routes (routes in routes) as well:

routes = [
  {
    path: '/faq/',
    url: './pages/faq.html',
  },
  {
    path: '/catalog/',
    url: './pages/catalog.html',
    routes: [
      {
        path: 'computers/',
        url: './pages/computers.html',
      },
      {
        path: 'monitors/',
        url: './pages/monitors.html',
      },
      ...
    ],
  }
];

What does it mean? To get better understanding, actually (under the hood) such routes will be merged into the following ones:

routes = [
  {
    path: '/faq/',
    url: './pages/faq.html',
  },
  {
    path: '/catalog/',
    url: './pages/catalog.html',
  }
  {
    path: '/catalog/computers/',
    url: './pages/computers.html',
  },
  {
    path: '/catalog/monitors/',
    url: './pages/monitors.html',
  },
];

So lets say we are on a /catalog/ page and have the following links:

  1. <a href="computers/">Computers</a> - will work as expected. Link will be merged with the current route (/catalog/ + computers/) and we will have /catalog/computers/ which we have in our routes.

  2. <a href="./computers/">Computers</a> - will work the same as case 1 because ./ in the beginning of path means same sub level.

  3. <a href="/catalog/computers/">Computers</a> - will also work as expected the same as case 1 because / (slash) in the beginning means root. And we have such root route in merged routes.

  4. <a href="/computers/">Computers</a> - won't work as expected because / (slash) in the beginning means root. And we don't have such /computers/ root route in our routes.

Detail Routes

For Master Detail view, it is also possible to specify detailRoutes in addition to master: true on master route.

When detailRoutes are specified then navigating to detail route will also preload its master route.

But unlike nested routes (specified in routes parameters), detail routes paths don't merged with master route path.

routes = [
  {
    path: '/blog/',
    url: './news.html',
    master: true,
    detailRoutes: [
      {
        /* We need to specify detail route path from root */
        path: '/blog/:postId/',
        url: './post.html',
      },
    ],
  },
  // ...
]

Routable Tabs

What routable tabs means and why is it good?

First of all we need to specify tabs routes in app routes. Lets assume we have a page with routable tabs on /tabs/ route:

routes = [
  {
    path: '/about-me/',
    url: './pages/about-me/index.html',
    // Pass "tabs" property to route
    tabs: [
      // First (default) tab has the same url as the page itself
      {
        path: '/',
        id: 'about',
        // Fill this tab content from content string
        content: `
          <div class="block">
            <h3>About Me</h3>
            <p>...</p>
          </div>
        `
      },
      // Second tab
      {
        path: '/contacts/',
        id: 'contacts',
        // Fill this tab content via Ajax request
        url: './pages/about-me/contacts.html',
      },
      // Third tab
      {
        path: '/cv/',
        id: 'cv',
        // Load this tab content as a component via Ajax request
        componentUrl: './pages/about-me/cv.html',
      },
    ],
  }
]

On the /about-me/ page we may have the following structure for example:

<div class="page">
  <div class="navbar">
    <div class="navbar-bg"></div>
    <div class="navbar-inner">
      <div class="title">About Me</div>
    </div>
  </div>
  <div class="toolbar tabbar toolbar-bottom">
    <div class="toolbar-inner">
      <a href="./" class="tab-link" data-route-tab-id="about">About</a>
      <a href="./contacts/" class="tab-link" data-route-tab-id="contacts">>Contacts</a>
      <a href="./cv/" class="tab-link" data-route-tab-id="cv">>CV</a>
    </div>
  </div>
  <div class="tabs tabs-routable">
    <div class="tab page-content" id="about"></div>
    <div class="tab page-content" id="contacts"></div>
    <div class="tab page-content" id="cv"></div>
  </div>
</div>

Almost the same as with usual Tabs but with the difference that there is no more tab-link-active and tab-active classes on tab links and tabs. These classes and tabs will be switched by router. And there is a new data-route-tab-id attribute, it is required for tabs switcher to understand which link related to the selected route.

You can learn more about Routable Tabs and their additional events in appropriate sections of Tabs component page.

Routable Modals

Modals can routable as well. By Modals here we mean the following components: Popup, Popover, Actions Sheet, Login Screen, Sheet Modal. Probably Popup and Login Screen have more use cases here.

And same features as for routable tabs and pages:

routes = [
  ...
  // Creates popup from passed HTML string
  {
    path: '/popup-content/',
    popup: {
      content: `
        <div class="popup">
          <div class="view">
            <div class="page">
              ...
            </div>
          </div>
        </div>
      `
    }
  },
  // Load Login Screen from file via Ajax
  {
    path: '/login-screen-ajax/',
    loginScreen: {
      url: './login-screen.html',
      /* login-screen.html contains:
        <div class="login-screen">
          <div class="view">
            <div class="page">
              ...
            </div>
          </div>
        </div>
      */
    },
  },
  // Load Popup from component file
  {
    path: '/popup-component/',
    loginScreen: {
      componentUrl: './popup-component.html',
      /* popup-component.html contains:
        <template>
          <div class="popup-screen">
            <div class="view">
              <div class="page">
                ...
              </div>
            </div>
          </div>
        </template>
        <style>...</style>
        <script>...</script>
      */
    },
  },
  // Use async route to check if the user is logged in:
  {
    path: '/secured-content/',
    async({ resolve }) {
      if (userIsLoggedIn) {
        resolve({
          url: 'secured-page.html',
        });
      } else {
        resolve({
          loginScreen: {
            url: 'login-screen.html'
          } ,
        });
      }
    },
  }
]

According to example above:

Routable Panels

Routable Panels available from Framework7 version 3.2.0.

Panels (Side Panels) can also be routable with same features as for routable modals and pages:

routes = [
  ...
  // Creates Panel from passed HTML string
  {
    path: '/left-panel/',
    panel: {
      content: `
        <div class="panel panel-left panel-cover">
          <div class="view">
            <div class="page">
              ...
            </div>
          </div>
        </div>
      `
    }
  },
  // Load Panel from file via Ajax
  {
    path: '/right-panel-ajax/',
    panel: {
      url: './right-panel.html',
      /* right-panel.html contains:
      <div class="panel panel-right panel-reveal">
        <div class="view">
          <div class="page">
            ...
          </div>
        </div>
      </div>
      */
    },
  },
  // Load Panel from component file
  {
    path: '/panel-component/',
    panel: {
      componentUrl: './panel-component.html',
      /* panel-component.html contains:
      <template>
        <div class="panel panel-left panel-cover">
          <div class="view">
            <div class="page">
              ...
            </div>
          </div>
        </div>
      </template>
      <style>...</style>
      <script>...</script>
      */
    },
  },
]

According to example above:

Note that routable Panels can't be mixed with static Panels. So if you have static left panel in the app, then only right panel can be loaded as routable panel.

Route Before Enter/Leave

beforeEnter and beforeLeave route hooks can be very useful if you need to do additional checks, execute additional actions or load/send something before route load (enter) and unload (leave). It can be single method or array of methods to be executed. For example:

routes = [
  {
    path: 'profile',
    url: 'profile.html',
    beforeEnter: function ({ resolve, reject }) {
      if (/* some condition to check user is logged in */) {
        resolve();
      } else {
        // don't allow to visit this page for unauthenticated users
        reject();
      }
    },

  },
  {
    path: 'profile-edit',
    url: 'profile-edit.html',
    beforeLeave: function ({ resolve, reject }) {
      if (/* user didn't save edited form */) {
        app.dialog.confirm(
          'Are you sure you want to leave this page without saving data?',
          function () {
            // proceed navigation
            resolve();
          },
          function () {
            // stay on page
            reject();
          }
        )
      } else {
        resolve();
      }
    }
  }
]

And of course there are multiple hooks are supported when passed as array of functions:

function checkAuth({ to, from, resolve, reject }) {
  if (/* some condition to check user is logged in */) {
    resolve();
  } else {
    reject();
  }
}
function checkPermission({ to, from, resolve, reject }) {
  if (/* some condition to check user edit permission */) {
    resolve();
  } else {
    reject();
  }
}

routes = [
  {
    path: '/profile/',
    url: 'profile.html',
    // check if the user is logged in
    beforeEnter: checkAuth,
  },
  {
    path: '/profile-edit/',
    url: 'profile-edit.html',
    // check if the user is logged in and has required permission
    beforeEnter: [checkAuth, checkPermission],
  }
]

Redirect & Alias

Alias

We can pass route alias using route alias property. Alias in this case basically means that same route can have multiple paths to access:

routes = [
  {
    path: '/foo/',
    url: 'somepage.html',
    alias: '/bar/',
  },
  {
    path: '/foo2/',
    url: 'anotherpage.html',
    alias: ['/bar2/', '/baz/', '/baz2/'],
  }
]

According to the example above:

Redirect

We can pass route redirect using redirect property:

For example:

routes = [
  {
    path: '/foo/',
    url: 'somepage.html',
  },
  {
    path: '/bar/',
    redirect: '/foo/',
  },
  {
    path: '/baz/',
    redirect: function ({to, resolve, reject}) {
      // if we have "user" query parameter
      if (to.query.user) {
        // redirect to such url
        resolve('/foo/?user=' + to.query.user);
      }
      // otherwise do nothing
      else reject();
    }
  }
]

Above example means:

Note in redirects we pass URL, not the route path like in alias

Keep Alive

keepAlive routes available from Framework7 version 3.6.0.

When keepAlive enabled, then once router loads such page, the page and, if applicable, its component (Vue, React or Router component) will be never destroyed. Instead, it will be detached from DOM and reused again when required.

It can be useful to enable for "heavy" pages which are not too often updated. For example, page with map, or with heavy canvas or other computation. With usual logic, all those heavy computations will happen each time this page is visited. But with keepAlive it will be done once, and with all next visits, router will reuse already rendered page DOM element.

It also can be useful if you really need to preserve page state. With enabled keepAlive, the next time page will be loaded, all DOM modifications and form elements state will be preserved.

But there are things to pay attention:

To avoid pitfalls with component's and page lifecycle, it is recommend to rely on following page events:

To make keepAlive route we just need to pass keepAlive: true to its parameters:

import SomPageComponent from './some-page.js';

var routes = [
  /* Usual route */
  {
    path: '/',
    url: './pages/home.html',
  },
  /* Alive route. Will be loaded from file first time, and then will reuse rendered DOM element */
  {
    path: '/map/',
    url: './pages/map.html',
    keepAlive: true,
  },

  /* Alive route. Will be created as component, and then will reuse rendered DOM element */
  {
    path: '/some-page/',
    component: SomPageComponent,
    keepAlive: true,
  },
];