Skip to content

Backbone History — Managing Browser Navigation

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Backbone History. We cover key concepts, practical examples, and best practices to help you master this topic.

Backbone.History is the engine behind Backbone's routing system. It listens for URL changes, matches them against defined routes, and dispatches the appropriate handler. It supports both hash-based URLs and HTML5 pushState.

What You'll Learn

You'll learn how Backbone.History works, how to configure it for hash and pushState modes, how it handles the back button, and how to manage URL fragments manually.

Why It Matters

Browser navigation is the foundation of UX in SPAs. Users expect the back button to work. Backbone.History makes this possible with minimal configuration.

Real-World Use

A multi-step configuration wizard uses Backbone.History to let users navigate between steps using the browser back/forward buttons. Each step has a unique URL that can be bookmarked.

flowchart LR
    A[popstate/hashchange] --> B[Backbone.History]
    B --> C{Match route?}
    C -->|Yes| D[Execute handler]
    C -->|No| E[Do nothing]
    D --> F[Update URL]
    F --> A

Starting History

Backbone.history.start() begins monitoring URL changes. It dispatches the current route immediately.

var Router = Backbone.Router.extend({
  routes: {
    '': 'home',
    'about': 'about'
  },
  home: function() { console.log('Home loaded'); },
  about: function() { console.log('About loaded'); }
});

var router = new Router();

// Start history — dispatches the current URL
Backbone.history.start();

console.log('Current fragment:', Backbone.history.fragment);

Expected output:

Home loaded
Current fragment: (based on current URL)

Hash-Based Routing (Default)

The default mode uses the URL hash: http://example.com/#tasks/42. This works without server configuration.

var Router = Backbone.Router.extend({
  routes: {
    'tasks': 'list',
    'tasks/:id': 'detail'
  },
  detail: function(id) {
    console.log('Hash routing. Task ID:', id);
    console.log('Fragment:', Backbone.history.fragment);
  }
});

var r = new Router();
Backbone.history.start();

r.navigate('tasks/99', { trigger: true });

Expected output:

Hash routing. Task ID: 99
Fragment: tasks/99

PushState (HTML5 History API)

PushState removes the hash, creating clean URLs: http://example.com/tasks/42. This requires server configuration to serve index.html for all routes.

var Router = Backbone.Router.extend({
  routes: {
    'tasks': 'list',
    'tasks/:id': 'detail'
  },
  detail: function(id) {
    console.log('PushState routing. Task ID:', id);
  }
});

var r = new Router();
Backbone.history.start({ pushState: true });

// Click a link — prevent default, navigate manually
$(document).on('click', 'a[href^="/"]', function(e) {
  e.preventDefault();
  var path = $(e.currentTarget).attr('href').replace(/^\//, '');
  r.navigate(path, { trigger: true });
});

Expected output:

PushState routing. Task ID: 99

Handling the Root

When the app lives in a subdirectory, set the root option.

// App served from http://example.com/myapp/
var Router = Backbone.Router.extend({
  routes: {
    '': 'home',
    'dashboard': 'dashboard'
  },
  home: function() { console.log('Home at root of app'); },
  dashboard: function() { console.log('Dashboard'); }
});

var r = new Router();
Backbone.history.start({
  root: '/myapp/'
});

console.log('Root set to:', Backbone.history.options.root);

Expected output:

Home at root of app
Root set to: /myapp/

Silent Start and Manual Dispatch

Use {silent: true} to start history without dispatching the current route. Useful when you want to handle the initial route manually.

var Router = Backbone.Router.extend({
  routes: {
    '': 'home',
    'settings': 'settings'
  },
  home: function() { console.log('Home'); },
  settings: function() { console.log('Settings'); }
});

var r = new Router();

// Start silently — no route dispatch
Backbone.history.start({ silent: true });
console.log('Started silently, fragment:', Backbone.history.fragment);

// Later, load the initial route
r.navigate(Backbone.history.fragment, { trigger: true });

Expected output:

Started silently, fragment: (current URL fragment)
Home

Custom URL Manipulation

Access Backbone.History directly for advanced URL operations.

var Router = Backbone.Router.extend({
  routes: { '*path': 'catchAll' },
  catchAll: function(path) { console.log('Route:', path); }
});

var r = new Router();
Backbone.history.start();

// Get current fragment
console.log('Current:', Backbone.history.fragment);

// Navigate and replace current history entry
r.navigate('tasks/1', { trigger: true, replace: true });

// Multiple navigations
r.navigate('tasks/2', { trigger: true });
r.navigate('tasks/3', { trigger: true });

// Go back in history
Backbone.history.on('route', function() {
  console.log('Back/forward pressed. Now at:', Backbone.history.fragment);
});

Expected output:

Current: (varies)
Route: tasks/1
Route: tasks/2
Route: tasks/3

Stopping History

Backbone.history.stop() stops listening for URL changes. Useful for cleanup in tests or when tearing down an app.

var Router = Backbone.Router.extend({
  routes: { 'test': 'test' },
  test: function() { console.log('Test route'); }
});

var r = new Router();
Backbone.history.start();

r.navigate('test', { trigger: true });

// Stop listening
Backbone.history.stop();
console.log('History stopped');

// This will NOT trigger the route
r.navigate('test', { trigger: true });
console.log('No route should fire after stop');

Expected output:

Test route
History stopped
No route should fire after stop

Common Mistakes

  1. Starting history before defining all routes. Routes defined after Backbone.history.start() are not matched. Define all routes first.
  2. Not configuring the server for pushState. Without server-side fallback to index.html, pushState URLs cause 404 errors on page refresh.
  3. Multiple Backbone.history.start() calls. Calling start twice throws an error. Check Backbone.History.started before starting.
  4. Using hash routing and pushState together. Choose one mode. Mixing them causes inconsistent behavior across browsers.
  5. Not calling navigate() with trigger:true for programmatic navigation. The URL changes but the View does not update. Always include trigger:true unless you intend to stay silent.

Practice Questions

  1. What is the difference between hash routing and pushState?
  2. What server configuration does pushState require?
  3. How do you navigate without adding a browser history entry?
  4. What does {silent: true} do when starting history?
  5. Challenge: Create a Router that supports both hash and pushState modes. Detect pushState support and fall back to hash if unavailable. Log which mode is active.

FAQ

Does Backbone.History work in older browsers?

Backbone.History uses hashchange event with fallback to polling. It works in IE8+.

What happens when the user clicks the back button?

Backbone.History detects the URL change and dispatches the matching route.

Can I have multiple History instances?

No. There is one global Backbone.history instance.

Does Backbone.History work with iframes?

Yes, but ensure the parent page and iframe have separate History instances.

How do I debug history issues?

Listen for route events and log Backbone.history.fragment to track navigation changes.

Mini Project

Create a simple app with three pages (Home, Products, Contact) using Backbone.History with hash routing. Add navigation links that use navigate() with trigger:true. Track route changes and log them. Add a back-button handler that detects when the user navigates back.

What's Next

Now that you understand History, learn Backbone Events for the custom event system. Then explore Backbone Event Aggregator for decoupled component communication.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro