Forem

moseeh
moseeh

Posted on

Building Modern SPAs with Vanilla JavaScript: A Beginner's Guide

Introduction

Single Page Applications (SPAs) provide a smooth user experience by dynamically updating the page without requiring a full reload. Unlike traditional multi-page applications, where navigating between pages triggers full server requests, SPAs only update relevant sections. This results in faster load times and a more interactive experience. In this guide, we'll build a basic SPA using only HTML, CSS, and JavaScript—no frameworks required.


What Makes a SPA Different?

Before diving into the code, let's break down the key differences between SPAs and traditional web applications:

  • Traditional Websites: Each navigation request loads a new HTML page from the server.
  • SPAs: The entire application loads once, and JavaScript dynamically updates the content based on user interactions.
  • Navigation in SPAs: URLs are managed using hash-based routing (#/home, #/about) or the History API.
  • Performance Benefits: SPAs feel faster since only necessary data is fetched, rather than reloading entire pages.

Now, let's get started by building our SPA step by step.


1. Setting Up the HTML Structure

The HTML serves as the foundation of our SPA. We'll keep it simple:

<!DOCTYPE html>
<html>
<head>
    <title>My SPA</title>
</head>
<body>
    <nav>
        <a href="#/">Home</a>
        <a href="#/about">About</a>
        <a href="#/contact">Contact</a>
    </nav>

    <div id="app"></div>

    <script src="app.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The <nav> contains links for navigation. Clicking a link updates the URL hash (e.g., #/about).
  • The <div id="app"> serves as the main container for dynamic content.
  • The JavaScript file (app.js) will handle routing and content updates.

2. Implementing a Simple Router

Routing is how SPAs switch between different "pages" without reloading. We'll use JavaScript to listen for URL hash changes and update the content accordingly.

Step 1: Define Page Content Functions

Each function returns the content for a specific page.

function getHomePage() {
    return `<h1>Welcome Home</h1><p>This is our SPA home page.</p>`;
}

function getAboutPage() {
    return `<h1>About Us</h1><p>Learn about our company.</p>`;
}

function getContactPage() {
    return `<h1>Contact Us</h1><p>Get in touch with our team.</p>`;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up the Routing Table

The routes object maps URL hashes to their respective functions.

const routes = {
    '#/': getHomePage,
    '#/about': getAboutPage,
    '#/contact': getContactPage
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Handle URL Changes

The handleRoute function updates the #app div whenever the URL hash changes.

function handleRoute() {
    const hash = window.location.hash || '#/'; // Default to home if no hash
    const content = routes[hash] ? routes[hash]() : '<h1>Page Not Found</h1>';
    document.getElementById('app').innerHTML = content;
}

// Listen for navigation events
window.addEventListener('hashchange', handleRoute);
window.addEventListener('load', handleRoute);
Enter fullscreen mode Exit fullscreen mode

3. Managing State in the SPA

State management keeps track of dynamic data in our app.

Step 1: Define a State Object

const state = {
    users: [],
    currentPage: 'home',
    isLoading: false
};
Enter fullscreen mode Exit fullscreen mode

Step 2: Update State and Refresh the View

function updateState(newState) {
    Object.assign(state, newState);
    renderContent();
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Render Content Based on State

function renderContent() {
    const appDiv = document.getElementById('app');
    if (state.isLoading) {
        appDiv.innerHTML = '<div>Loading...</div>';
        return;
    }
    appDiv.innerHTML = routes[window.location.hash || '#/']();
}
Enter fullscreen mode Exit fullscreen mode

4. Handling User Interactions

We'll use event delegation to handle user actions dynamically.

Step 1: Add Event Listeners

function setupEventListeners() {
    document.getElementById('app').addEventListener('click', function(event) {
        if (event.target.matches('.button-submit')) {
            handleSubmit(event);
        }
        if (event.target.matches('.toggle-menu')) {
            toggleMenu(event);
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement Event Handlers

function handleSubmit(event) {
    event.preventDefault();
    updateState({ isLoading: true });
}

function toggleMenu(event) {
    document.querySelector('.menu').classList.toggle('active');
}
Enter fullscreen mode Exit fullscreen mode

5. Making the SPA Real-time with WebSockets

WebSockets allow real-time updates, making our SPA dynamic.

Step 1: Establish a WebSocket Connection

const socket = new WebSocket('ws://your-server-url');
Enter fullscreen mode Exit fullscreen mode

Step 2: Handle Incoming Messages

socket.addEventListener('message', function(event) {
    const data = JSON.parse(event.data);
    updateState({ users: data.users, isLoading: false });
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Send Messages

function sendMessage(message) {
    if (socket.readyState === WebSocket.OPEN) {
        socket.send(JSON.stringify(message));
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices for SPAs

  1. Keep It Simple – Start small and test each feature independently.
  2. Organize Code Properly – Use modular functions and meaningful variable names.
  3. Handle Errors Gracefully – Provide fallback UI elements.
  4. Optimize Performance – Reduce DOM updates and use event delegation.

Conclusion

By following this guide, you've built a fully functional SPA using vanilla JavaScript. This foundational knowledge will help you understand how frameworks like React and Vue work behind the scenes. Experiment by adding features such as local storage, animations, or API integrations to enhance your SPA.

Happy coding 🚀

Top comments (0)