Skip to content

What Are SPAs — Single-Page Applications Explained for Beginners

DodaTech Updated 2026-06-28 5 min read

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

Single-Page Applications load a single HTML page and dynamically update content via JavaScript, providing smooth app-like experiences without full page reloads unlike traditional multi-page websites.

What You'll Learn

By the end of this tutorial, you will understand what SPAs are, how they work under the hood, their benefits and drawbacks, and when to use them.

Why It Matters

SPAs power most modern web applications — Gmail, Google Maps, Facebook, Twitter, and Trello all use SPA architecture. Understanding SPAs is essential for building modern interactive web applications that users expect to feel instant and responsive.

Real-World Use

Gmail loads a single HTML page and then fetches emails via AJAX as you navigate. Clicking a label or email does not reload the page — the UI updates instantly. This makes Gmail feel like a desktop application rather than a website.

How SPAs Work

SPA Architecture
    Initial Load: Server sends HTML + CSS + JS bundle
         ↓
    Browser renders page and executes JavaScript
         ↓
    JavaScript initializes the app
    (router, state management, components)
         ↓
    ┌──────────────────────────────────────────┐
    │         User Interaction                 │
    │  Click link → Router intercepts          │
    │  → Updates URL (pushState)               │
    │  → Fetches data (fetch/XHR)              │
    │  → Renders new component                 │
    │  → Updates DOM without reload            │
    └──────────────────────────────────────────┘
         ↓
    NO full page reloads during use

Think of an SPA like a well-organized desk. The initial load is like setting up your desk with all the tools you might need (pens, paper, computer). Once set up, you do not have to rebuild the desk every time you switch tasks — you just pick up a different tool (component) and keep working.

Basic SPA Architecture

// Minimal SPA router
const routes = {
    '/': { title: 'Home', render: () => '<h1>Home</h1><p>Welcome to the SPA</p>' },
    '/about': { title: 'About', render: () => '<h1>About</h1><p>About this application</p>' },
    '/contact': { title: 'Contact', render: () => '<h1>Contact</h1><p>Contact us here</p>' }
};

function navigate(path) {
    // Update browser URL without page reload
    history.pushState(null, '', path);

    // Update page title
    document.title = routes[path]?.title || 'SPA';

    // Render new content
    const app = document.getElementById('app');
    app.innerHTML = routes[path]?.render() || '<h1>404 Not Found</h1>';
}

// Intercept link clicks
document.addEventListener('click', (event) => {
    const link = event.target.closest('a');
    if (link && link.href.startsWith(window.location.origin)) {
        event.preventDefault();
        navigate(new URL(link.href).pathname);
    }
});

// Handle browser back/forward
window.addEventListener('popstate', () => {
    navigate(window.location.pathname);
});

// Initial render
navigate(window.location.pathname);

Output:

URL: https://example.com/about
Page title: About
Content displayed: About this application
No page reload occurred

SPA vs MPA Comparison

// SPA: Single HTML file, dynamic content
// MPA: Multiple HTML files, server-rendered

// SPA navigation flow
navigator.serviceWorker?.controller?.postMessage('hello');
fetch('/api/data').then(data => renderUI(data));
history.pushState(null, '', '/new-page');

// MPA navigation flow
// Browser loads entirely new HTML document from server
// <a href="/new-page"> → full page reload → new HTML

Client-Side Routing

SPAs use two routing approaches:

History API: Clean URLs like /users/123 using pushState() and popstate. Requires server configuration to serve the SPA for all routes.

Hash Routing: URLs like /#/users/123 using the hash fragment. Does not require server configuration but produces less clean URLs.

// History API routing
history.pushState({ userId: 123 }, '', '/users/123');
window.addEventListener('popstate', (event) => {
    console.log('Navigated to:', event.state);
    renderUser(event.state.userId);
});

// Hash routing
window.addEventListener('hashchange', () => {
    const path = window.location.hash.slice(1) || '/';
    console.log('Hash route:', path);
    renderRoute(path);
});

Data Fetching

SPAs fetch data asynchronously after the initial page load:

async function loadUserData(userId) {
    try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Failed to load user');

        const user = await response.json();
        renderUserProfile(user);
    } catch (error) {
        renderError(`Could not load user: ${error.message}`);
    }
}

function renderUserProfile(user) {
    document.getElementById('app').innerHTML = `
        <h1>${user.name}</h1>
        <p>Email: ${user.email}</p>
        <p>Role: ${user.role}</p>
        <button onclick="loadUserData(${user.id + 1})">Next User</button>
    `;
}

Common Mistakes

  1. Forgetting server-side route fallback. When using History API routing, configure your server to serve index.html for all routes. Otherwise, refreshing a deep link returns 404.
  2. Not handling loading states. API calls take time. Always show loading indicators during data fetching to prevent blank screens.
  3. Memory leaks from event listeners. SPAs run for a long time. Unused event listeners accumulate. Clean up with removeEventListener.
  4. Ignoring the back button. Users expect browser back/forward to work. Always handle the popstate event.
  5. Large initial bundle. Loading all JavaScript upfront defeats the purpose of SPAs. Implement Code Splitting to load routes on demand.

Practice Questions

  1. What is the fundamental difference between an SPA and an MPA?
  2. How does client-side routing work in SPAs?
  3. What are the two main approaches to client-side routing?
  4. Why must you configure server fallback for History API routing?
  5. What problem does code splitting solve in SPAs?

Challenge: Build a minimal SPA with three routes (home, about, contact) using the History API. Implement click interception on links, popstate handling for back/forward, and a loading state during simulated data fetch.

FAQ

Do SPAs work without JavaScript?

No. SPAs require JavaScript to render content. This means they do not work without JS, which affects SEO and accessibility. Use SSR or progressive enhancement to address this.

Are SPAs better than MPAs?

Neither is universally better. SPAs excel at highly interactive applications. MPAs are better for content-heavy sites. Choose based on your use case.

How do SPAs affect SEO?

SPAs have poor SEO by default because crawlers execute limited JavaScript. Solutions include SSR (server-side rendering), pre-rendering, or dynamic rendering.

What frameworks are best for building SPAs?

React, Vue, and Angular are the most popular. Svelte and Solid.js are newer alternatives with different tradeoffs. Choose based on team expertise and project needs.

Do SPAs use more memory than MPAs?

Yes, SPAs typically use more memory because the application stays loaded in the browser for the entire session. This can be a concern on low-end devices.

Mini Project

Build a simple SPA with a todo list: three routes (home showing stats, todos showing the list, about), client-side routing with History API, data fetching from a mock API (localStorage), and basic state management using a global store object.

What's Next

You understand what SPAs are. Next, compare SPA vs MPA in depth to understand when each architecture is the right choice.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro