Vanilla Spas

Master single-page application architecture using pure JavaScript — routing, views, state management, and professional patterns.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

A Single Page Application is a web app where the page loads once and navigation happens client-side. New content is injected into the page dynamically without browser reloads.

💡 Real-World SPAs: YouTube, Instagram, Twitter, and most dashboard apps use SPA architecture. Building SPAs with pure JavaScript helps you understand how frameworks like React and Vue work internally.

To build your own SPA, you need these components:

Determines which page/view to show based on URL

Uses the URL hash (#). Doesn't reload the page and works everywhere. Easiest to implement.

Clean URLs without hashes. Uses pushState() and popstate. Requires server configuration.

💡 Which to Choose: For beginners, use hash routing. For production apps, History API gives cleaner URLs but requires server rewrites to serve index.html for all routes.

Each page is a function that returns HTML (as a string or DOM elements).

Create a router that loads views based on the URL hash:

Since the DOM is replaced on navigation, you can't attach events directly to elements that might disappear.

💡 Why This Matters: Event delegation is essential for SPAs because the DOM changes frequently. By listening on a parent element (like document), your handlers automatically work for dynamically inserted elements.

SPAs need to track application state (user data, UI state, etc.) and re-render when it changes.

Real apps need dynamic routes. Here's how to add parameter support:

Load pages only when needed for massive performance gains:

💡 Why Lazy Load: With lazy loading, users only download the JavaScript they need. A 500KB app might only load 50KB initially, making the first page load much faster.

Control access to routes based on authentication or other conditions:

Structure your UI with reusable components, similar to React:

Build a reusable router class like React Router or Vue Router:

Professional SPAs show loading states and handle errors gracefully:

Add CSS transitions between views for a polished feel:

SPAs must defend against XSS and other injection attacks:

💡 Security Rule: Never trust user input. Always escape or sanitize any data that comes from users, URLs, or APIs before inserting into the DOM.

For History API routing, configure your server to serve index.html for all routes:

Here's a complete working SPA in one file to get you started:

💡 Try It: Copy this HTML into a file and open it in your browser. Click the navigation links and try the counter — all without page reloads!

Practice quiz

What defines a Single Page Application (SPA)?

  • It has only one HTML element
  • It cannot use JavaScript
  • The page loads once and navigation happens client-side without full reloads
  • It always needs a database

Answer: The page loads once and navigation happens client-side without full reloads. In an SPA the page loads once and navigation injects new content client-side without browser reloads.

Which routing strategy is easiest and works everywhere with no server config?

  • Hash-based routing (#home, #about)
  • History API routing
  • Server-side routing
  • DNS routing

Answer: Hash-based routing (#home, #about). Hash-based routing uses the URL hash, requires no server configuration, and works everywhere.

Which browser event fires when the URL hash changes?

  • popstate
  • load
  • click
  • hashchange

Answer: hashchange. The 'hashchange' event fires when the URL hash changes, letting the router respond.

What does the History API use to change the URL without reloading?

  • location.reload()
  • history.pushState()
  • document.write()
  • window.open()

Answer: history.pushState(). History API routing uses history.pushState() to change the URL and listens to popstate for back/forward.

Why does History API routing require server configuration?

  • The server must serve index.html for all routes so deep links and refreshes work
  • It needs a database
  • It uses WebSockets
  • It needs HTTPS only

Answer: The server must serve index.html for all routes so deep links and refreshes work. Clean URLs require server rewrites that return index.html for every route, or refreshes would 404.

In the lesson, how is each view typically represented?

  • As a CSS class
  • As a database row
  • As a function that returns HTML (a string or DOM)
  • As an image

Answer: As a function that returns HTML (a string or DOM). Each page/view is a function that returns HTML, which the router injects into the app container.

Why is event delegation essential in SPAs?

  • It makes CSS faster
  • Because the DOM is replaced on navigation, listening on a parent (like document) keeps handlers working for dynamic elements
  • It encrypts events
  • It prevents reloads

Answer: Because the DOM is replaced on navigation, listening on a parent (like document) keeps handlers working for dynamic elements. Since views replace the DOM, you listen on a stable parent like document so handlers still work after navigation.

How does the lesson capture a route parameter like '/user/:id'?

  • With a regex on the whole URL
  • With localStorage
  • By reloading the page
  • By splitting route and path into segments and capturing parts starting with ':'

Answer: By splitting route and path into segments and capturing parts starting with ':'. matchRoute splits both into segments; any segment starting with ':' is captured as a named parameter.

What is the benefit of lazy loading routes with dynamic imports?

  • It removes the need for routing
  • Users only download the JavaScript they need, speeding up the first load
  • It disables caching
  • It prevents XSS

Answer: Users only download the JavaScript they need, speeding up the first load. Dynamic imports load a view's module only when needed, so the initial bundle is much smaller.

How does the lesson recommend preventing XSS when inserting user data?

  • Always use innerHTML with the raw input
  • Disable JavaScript
  • Use textContent or escape/sanitize user input before inserting it
  • Store input in cookies

Answer: Use textContent or escape/sanitize user input before inserting it. Never insert raw user input as HTML; use textContent or escape/sanitize (e.g., DOMPurify) to prevent XSS.