Router for Web Components

Works with Polymer, X-Tag, and natively with the platform polyfill.

Lazy-loads content. Binds path variables and query parameters to the page element's attributes. Supports multiple layouts. Works with hashchange and HTML5 pushState. One set of routes will match regular paths /, hash paths #/, and hashbang paths #!/.

Download or run bower install app-router --save.


<!doctype html>
    <title>App Router</title>
    <link rel="import" href="/bower_components/app-router/app-router.html">
      <!-- matches an exact path -->
      <app-route path="/home" import="/pages/home-page.html"></app-route>

      <!-- matches using a wildcard -->
      <app-route path="/customer/*" import="/pages/customer-page.html"></app-route>

      <!-- matches using a path variable -->
      <app-route path="/order/:id" import="/pages/order-page.html"></app-route>

      <!-- matches a pattern like '/word/number' -->
      <app-route path="/^\/\w+\/\d+$/i" regex import="/pages/regex-page.html"></app-route>

      <!-- matches everything else -->
      <app-route path="*" import="/pages/not-found-page.html"></app-route>

Changing the URL will find the first app-route that matches, load the element or template, and replace the current view.

Data Binding

Path variables and query parameters automatically attach to the element's attributes.

<!-- url -->

<!-- route -->
<app-route path="/order/:id" import="/pages/order-page.html"></app-route>

<!-- will bind 123 to the page's `id` attribute and "ascending" to the `sort` attribute -->
<order-page id="123" sort="ascending"></order-page>

See it in action here.

Multiple Layouts

Each page chooses which layout to use. This allows multiple layouts in the same app. Use <content> tag insertion points to insert the page into the layout. This is similar to nested routes but completely decouples the page layout from the router.

This is a simple example showing a page and it's layout.


<link rel="import" href="/layouts/simple-layout.html">
<polymer-element name="home-page" noscript>
      <div class="title">Home</div>
      <p>The home page!</p>


<polymer-element name="simple-layout" noscript>
      <content select=".title"><!-- content with class 'title' --></content>
    <content><!-- all other content --></content>

<app-route> options

import a custom element

Lazy-load a custom element.

<app-route path="/customer/:customerId" import="/pages/customer-page.html"></app-route>

When you navigate to /customer/123 the router will load /pages/customer-page.html, replace the active view with a new customer-page element, and bind any attributes element.setAttribute('customerId', 123).

You can manually set the element‘s name with the element attribute if it’s different from the file name. This is useful when bundling (vulcanizing) custom elements.

<app-route path="/customer/:customerId" import="/pages/page-bundle.html" element="customer-page"></app-route>

pre-loaded custom element

You can route to a pre-loaded custom element. In this case, load the element normally in the <head> and include the element="element-name" attribute on the route. This is how you'd bundle and pre-load custom elements.

  <link rel="import" href="/pages/page-bundle.html">
  <app-route path="/customer/:customerId" element="customer-page"></app-route>

import template

You can use a <template> instead of a custom element. This doesn't have data binding and is lighter-weight than a custom element. Just include the template attribute.

<app-route path="/example" import="/pages/template-page.html" template></app-route>

inline template

Finally, you can in-line a <template> like this.

<app-route path="/example" template>
    <p>Inline template FTW!</p>

regular expressions

Include the regex attribute to match on a regular expression. The format is the same as a JavaScript regular expression.

<!-- matches a pattern like '/word/number' -->
<app-route path="/^\/\w+\/\d+$/i" regex import="/pages/regex-page.html"></app-route>

Note: The regular expression must start with a / and end with a / optionally followed by i. Options global g, multiline m, and sticky y aren't valid when matching paths.

<app-router> options

Trailing Slashes

By default /home and /home/ are treated as separate routes. You can configure the router to ignore trailing slashes with trailingSlash="ignore".

<app-router trailingSlash="ignore">
  <!-- matches '/home' and '/home/' -->
  <app-route path="/home" import="/pages/home-page.html"></app-route>


There are three ways change the active route. hashchange, pushState(), and a full page load.


If you‘re using hashchange you don’t need to do anything. Clicking a link <a href="/#/new/page">New Page</a> will fire a hashchange event and tell the router to load the new route. You don't need to handle the event in your code.


If you're using HTML5 pushState you need one extra step. The pushState() method was not meant to change the page, it was only meant to push state into history. This is an “undo” feature for single page applications. To use pushState() to navigate to another route you need to call it like this.

history.pushState(stateObj, title, '/new/page'); // push a new URL into the history stack
history.go(0); // go to the current state in the history stack, this fires a popstate event

Full page load

Clicking a link <a href="/new/page">New Page</a> without a hash path will do a full page load. You need to make sure your server will return index.html when looking up the resource at /new/page. The simplest set up is to always return index.html and let the app-router handle the routing including a not found page.

Demo Site & Example Setup

Check out the app-router in action at

You can download an example setup here to get running locally.

Build, Test, and Debug Build Status

Source files are under the src folder. The build process writes to the root directory. The easiest way to debug is to include the source script rather than the minified HTML import.

<script src="/bower_components/app-router/src/app-router.js"></script>

To build: