# How Single-Page Application Routing Works in the Bank Project

> Learn how the bank project uses vanilla JavaScript and the HTML5 History API for seamless single-page application routing without page reloads.

- Repository: [Microsoft/Web-Dev-For-Beginners](https://github.com/microsoft/Web-Dev-For-Beginners)
- Tags: deep-dive
- Published: 2026-02-27

---

**The bank project implements a vanilla-JavaScript single-page application router using the HTML5 History API to swap HTML templates dynamically without page reloads.**

The banking demo in the `microsoft/Web-Dev-For-Beginners` repository demonstrates single-page application routing without React, Vue, or other frameworks. By combining the History API with native HTML `<template>` elements, the app renders distinct login and dashboard views while preserving standard browser navigation patterns. This lightweight approach reveals the fundamental mechanics underlying all client-side routers.

## Core Routing Architecture

The SPA routes are defined in [`7-bank-project/solution/app.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/7-bank-project/solution/app.js) as a plain JavaScript object that maps URL paths to configuration objects. Each route specifies a page title, a template ID, and an optional initialization function.

```javascript
// app.js (lines 20-24)
const routes = {
  '/dashboard': { title: 'My Account', templateId: 'dashboard', init: refresh },
  '/login':     { title: 'Login',      templateId: 'login',    init: attachAuthHandlers }
};

```

The HTML layer provides the mounting point and view templates in [`7-bank-project/solution/index.html`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/7-bank-project/solution/index.html). The `<div id="app">` serves as the active content container, while `<template>` elements contain dormant markup for each page.

```html
<!-- index.html -->
<div id="app" aria-live="polite">Loading…</div>

<template id="login">
  <section class="login-page">…</section>
</template>

<template id="dashboard">
  <section class="dashboard-page">…</section>
</template>

```

## The Routing Lifecycle

When a user clicks a navigation link or submits a form, the router executes a six-step sequence to update the URL and render the corresponding view.

### 1. Navigation Request

The `navigate()` function initiates route changes by pushing a state object onto the browser history stack and immediately invoking `updateRoute()`.

```javascript
// app.js (lines 25-29)
function navigate(path) {
  history.pushState({ path }, '', path);
  updateRoute();
}

```

### 2. Route Resolution

Inside `updateRoute()`, the router determines the current path by checking `history.state.path` first, then falling back to `window.location.pathname`. It looks up the matching configuration in the `routes` object, defaulting to `/dashboard` if the path is unrecognized.

```javascript
// app.js (lines 31-34)
const path = history.state?.path || window.location.pathname;
const route = routes[path] || routes['/dashboard'];

```

### 3. Template Rendering

The function retrieves the relevant `<template>` element, clones its content, and injects the fragment into the DOM mount point. This approach avoids string-based HTML injection by using the `content` property of `<template>` elements.

```javascript
// app.js (lines 35-39)
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);

```

### 4. Post-Render Wiring

After DOM insertion, `attachGlobalHandlers()` binds event listeners for logout buttons and other global UI elements. If the route configuration includes an `init` function (such as `refresh` for the dashboard or `attachAuthHandlers` for login), the router executes it asynchronously.

```javascript
// app.js (lines 41-46)
attachGlobalHandlers();
if (typeof route.init === 'function') {
  Promise.resolve(route.init()).catch(err => console.error(err));
}

```

### 5. Browser Navigation Events

To support back and forward button usage, the code listens for `popstate` events. When the user navigates through history, the event triggers `updateRoute()` to synchronize the UI with the current URL.

```javascript
// app.js (lines 50-52)
window.addEventListener('popstate', () => updateRoute());

```

### 6. Initial Application Load

The `init()` function bootstraps the application on page load. It checks existing authentication state to determine the starting path—`/dashboard` for logged-in users or `/login` for anonymous visitors—and seeds the history stack using `replaceState()` to avoid creating duplicate entries.

```javascript
// app.js (lines 96-103)
if (!history.state || !history.state.path) {
  const initialPath = state.account ? '/dashboard' : '/login';
  history.replaceState({ path: initialPath }, '', initialPath);
}
updateRoute();

```

## Extending the Router

Adding new pages requires only a template definition and a route entry. No build step or dependency installation is necessary.

**To add a static About page:**

1. Define the route in [`app.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/app.js):

```javascript
const routes = {
  '/dashboard': { title: 'My Account', templateId: 'dashboard', init: refresh },
  '/login':     { title: 'Login',      templateId: 'login',    init: attachAuthHandlers },
  '/about':     { title: 'About Us',   templateId: 'about' }
};

```

2. Create the template in [`index.html`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/index.html):

```html
<template id="about">
  <section class="page">
    <h1>About Squirrel Banking</h1>
    <p>This demo showcases single-page application routing with vanilla JavaScript.</p>
  </section>
</template>

```

**Programmatic navigation** anywhere in the codebase simply requires calling `navigate()`:

```javascript
// Redirect to dashboard after successful API login
navigate('/dashboard');

```

## Summary

- The bank project uses a **route table** in [`app.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/app.js) (lines 20-24) to map URL paths to HTML template IDs and initialization functions.
- **History API methods** (`pushState`, `replaceState`) update the URL without server requests, while `popstate` listeners (lines 50-52) handle browser back/forward buttons.
- The `updateRoute()` function (lines 31-46) resolves paths, clones `<template>` content, and mounts fragments into `#app`.
- Route-specific logic executes through optional `init` callbacks defined in the route configuration object.

## Frequently Asked Questions

### How does this vanilla JavaScript router differ from React Router or Vue Router?

Unlike framework-specific routers that use virtual DOM diffing, this implementation manipulates the actual DOM directly by cloning HTML `<template>` elements. It lacks features like nested routes or lazy loading, but demonstrates the core History API mechanics that power all SPA routers. The Microsoft Web-Dev-For-Beginners project intentionally uses this minimal pattern to teach routing fundamentals without framework abstractions.

### Why does the router use HTML template elements instead of innerHTML strings?

The `<template>` element provides a `content` DocumentFragment that can be cloned and inserted without parsing HTML strings or exposing the application to XSS vulnerabilities via `innerHTML` assignment. This approach keeps markup declarative in the HTML file while JavaScript handles the logic, separating concerns more cleanly than string concatenation.

### How does the router handle direct URL access or page refreshes?

On initial load, the `init()` function (lines 96-103) checks `history.state` and uses `history.replaceState()` to establish the correct entry point based on authentication status. However, this specific implementation requires a static server configuration that serves [`index.html`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/index.html) for all routes, as the server-side routing is not handled by this client-side code alone.

### Can this routing pattern scale to larger applications?

While functional for demos, this pattern lacks features required for complex applications: route guards, code splitting, nested layouts, and error boundaries. The route table is a flat object that could grow unwieldy, and global event handlers require manual cleanup to prevent memory leaks. For production software, migrating to a framework-specific router or a library like `navigo` or [`page.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/page.js) is recommended.