jashkenas/backbone · error · Error

Backbone.history has already been started

Error message

Backbone.history has already been started

What it means

Backbone.History.start() initializes client-side routing (hashchange/pushState listening), and History.started is a global flag. Starting it twice would register duplicate listeners and re-initialize routing state, so Backbone guards with this explicit error. Only one history instance should ever be started per page load.

Source

Thrown at backbone.js:1880

      return path.charAt(0) === '/' ? path.slice(1) : path;
    },

    // Get the cross-browser normalized URL fragment from the path or hash.
    getFragment: function(fragment) {
      if (fragment == null) {
        if (this._usePushState || !this._wantsHashChange) {
          fragment = this.getPath();
        } else {
          fragment = this.getHash();
        }
      }
      return fragment.replace(routeStripper, '');
    },

    // Start the hash change handling, returning `true` if the current URL matches
    // an existing route, and `false` otherwise.
    start: function(options) {
      if (History.started) throw new Error('Backbone.history has already been started');
      History.started = true;

      // Figure out the initial configuration. Do we need an iframe?
      // Is pushState desired ... is it available?
      this.options          = _.extend({root: '/'}, this.options, options);
      this.root             = this.options.root;
      this._trailingSlash   = this.options.trailingSlash;
      this._wantsHashChange = this.options.hashChange !== false;
      this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
      this._useHashChange   = this._wantsHashChange && this._hasHashChange;
      this._wantsPushState  = !!this.options.pushState;
      this._hasPushState    = !!(this.history && this.history.pushState);
      this._usePushState    = this._wantsPushState && this._hasPushState;
      this.fragment         = this.getFragment();

      // Normalize root to always include a leading and trailing slash.
      this.root = ('/' + this.root + '/').replace(rootStripper, '/');

View on GitHub (pinned to f229c75b19)

Solutions

  1. Guard the call: if (!Backbone.History.started) Backbone.history.start();
  2. Move start() to a single, top-level entry point and remove duplicate invocations.
  3. Track your own initialized flag around your bootstrap function so it runs only once.
  4. Ensure the bootstrap script/module isn't loaded or evaluated twice (check script tags and module loading).

Example fix

// before
Backbone.history.start();
// ...later, possibly again:
Backbone.history.start(); // throws

// after
if (!Backbone.History.started) {
  Backbone.history.start({pushState: true});
}
Defensive patterns

Strategy: validation

Validate before calling

if (!Backbone.History.started) {
  Backbone.history.start({pushState: true, root: '/'});
}

Try / catch

try {
  Backbone.history.start();
} catch (e) {
  if (e.message !== 'Backbone.history has already been started') throw e;
  // already started: safe to ignore
}

Prevention

When it happens

Trigger: Calling Backbone.history.start() (or a History subclass instance's start()) more than once in the same page — e.g., calling it both in app bootstrap code and again in a router initialization or an AJAX-complete callback, or re-running a bootstrap script on route change.

Common situations: Duplicated bootstrap code (require/AMD module loaded twice, script included twice); legacy code paths that re-call start() after navigation; frameworks wrapping Backbone that call start() internally while app code also calls it; hot-reload setups re-executing initialization modules.

Related errors


AI-assisted analysis of jashkenas/backbone@f229c75b19 (2026-08-28). Data as JSON: /api/errors/c3e13a6dc9f41048. Report an issue: GitHub.