All questions

History Router

Premium

History Router

The # in a hash router is a giveaway that you're faking navigation. The History API removes it: history.pushState changes the URL to a clean /users/42 and adds a history entry without a page load, and the popstate event tells you when the user hits back or forward. That's how modern SPA routers (React Router, Vue Router) produce real-looking URLs. The catch — and the whole lesson here — is that pushState is silent: it fires no event, so your own navigations must resolve manually.

Implement historyRouter() with on, subscribe, start, navigate, and stop. See MDN: History API.

Signature

function historyRouter() {
  return { on, subscribe, start, navigate, stop };
}

Examples

const router = historyRouter()
  .on('/users/:id', (params) => render(User, params))
  .on('/', () => render(Home));

router.start();               // resolves the current pathname
router.navigate('/users/42'); // pushState -> URL is '/users/42' (no #), then resolve
// user clicks Back -> popstate -> router resolves '/' -> Home

Notes

  • Clean paths — the path is location.pathname (/users/42), no #; an empty path means /.
  • :name segments are params — decode the captured values, like the hash router.
  • pushState fires nothing — after history.pushState, call resolve yourself; only back/forward fire popstate.
  • stop() removes the popstate listener. First match wins; unmatched navigations still notify subscribers with matched: false.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium