DEV Community

Cover image for Single Page Application Routing Using Hash or URL
The Dev Drawer
The Dev Drawer

Posted on • Updated on

Single Page Application Routing Using Hash or URL

One of the most asked code questions during a front-end interview is "Can you create a single page application with routes, without a framework?" In this tutorial, I show you how to create a custom routing system for your single page application using either the hash or URL method...without a framework.

This tutorial shows you how to build a Single Page App using vanilla JavaScript. I show you how to implement client-side routing both ways (hash or URL) in an easy-to-use format that can be replicated for any project.

View This On YouTube

Folder Structure

We are using a basic HTML structure. You can set up your files however you like but for the sake of this tutorial, you can replicate what I have below.

index.html
/templates
   404.html
   index.html
   about.html
   contact.html
/js/
   router.js
Enter fullscreen mode Exit fullscreen mode

Let's Create Our HTML

We are going to create a basic HTML document to serve as the main page. On this page, we are going to have a nav section and a content section. You can build this out however you want but note the <nav></nav> tags are used for URL routing so your nav needs to be present within those tags if you are going the URL method.

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title></title>
   </head>
   <body>
      <nav>
      </nav>
      <div id="content"></div>
   </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Creating the JS File

Before the </body> closing tag, you need to add this reference to the JS file you created above.

<script src="/js/router.js"></script>

Option One: URL Routing

First, we are going to go over how to do this with URL routing. This means that your links will look like /about. This is the typical look of a URL. the hash method uses # to break up the pages. I will go over that further down.

Use Case: Websites

This option is better for SEO and is more user-friendly.

NOTE: There are some downsides to using this method. You need to configure the webserver to serve the index.html for SPA Route Paths. You can do this at the server level but if you are using something like VS Code LIVE SERVER, you cannot. This means if you navigate directly to /about the server will not render the file as it needs to load the index.html scripts first. You can modify your .htaccess file to accomplish this.

Add the HTML navigation

Add the following between the <nav></nav> tags in your index.html file.

<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
Enter fullscreen mode Exit fullscreen mode

Add the navigation links in HTML

Now, let's get started on the JS. First, we want to make any link in the <nav></nav> tags use our routing.

// create document click that watches the nav links only
document.addEventListener("click", (e) => {
    const { target } = e;
    if (!target.matches("nav a")) {
        return;
    }
    e.preventDefault();
    route();
});
Enter fullscreen mode Exit fullscreen mode

Create the Routes

Each route will have an object array associated with it. This will tell the script what the URL reference is, as well as what template, title, and description are to be used.

const routes = {
    404: {
        template: "/templates/404.html",
        title: "404",
        description: "Page not found",
    },
    "/": {
        template: "/templates/index.html",
        title: "Home",
        description: "This is the home page",
    },
    "/about": {
        template: "/templates/about.html",
        title: "About Us",
        description: "This is the about page",
    },
    "/contact": {
        template: "/templates/contact.html",
        title: "Contact Us",
        description: "This is the contact page",
    },
};
Enter fullscreen mode Exit fullscreen mode

Create a function that watches the URL and calls the urlLocationHandler

const route = (event) => {
    event = event || window.event; // get window.event if event argument not provided
    event.preventDefault();
    // window.history.pushState(state, unused, target link);
    window.history.pushState({}, "", event.target.href);
    locationHandler();
};
Enter fullscreen mode Exit fullscreen mode

Create a function that handles the URL location

const locationHandler = async () => {
    const location = window.location.pathname; // get the url path
    // if the path length is 0, set it to primary page route
    if (location.length == 0) {
        location = "/";
    }
    // get the route object from the urlRoutes object
    const route = routes[location] || routes["404"];
    // get the html from the template
    const html = await fetch(route.template).then((response) => response.text());
    // set the content of the content div to the html
    document.getElementById("content").innerHTML = html;
    // set the title of the document to the title of the route
    document.title = route.title;
    // set the description of the document to the description of the route
    document
        .querySelector('meta[name="description"]')
        .setAttribute("content", route.description);
};
Enter fullscreen mode Exit fullscreen mode

Finishing The Script

Lastly, we need to call the function when the page is first loaded, or else the home page will not work unless it is clicked on. We also need to add a watcher for the URL changes so the script knows when to show new content.

// add an event listener to the window that watches for url changes
window.onpopstate = locationHandler;
// call the urlLocationHandler function to handle the initial url
window.route = route;
// call the urlLocationHandler function to handle the initial url
locationHandler();
Enter fullscreen mode Exit fullscreen mode

Option Two: Hash Routing

Please replace the content of your router.js file with the following code if you are using the hash method.

Now, on to the second option. Hash routing is more common if you are using a framework but if you are creating it from scratch, the negative SEO benefit may make you shy away. This means that your links will look like #about instead of the typical URL method above. For some, this URL type may not be optimal due to it being so different than what your users are used to. Otherwise, the code is very similar to the URL method...even shorter.

Use Case: Apps, Landing Pages

NOTE: There are some downsides to using this method. sing hashes may not be the best route for SEO and it may also be unusual for some users which may make them not use the website.

Add the HTML navigation

Add the following between the <nav></nav> tags in your index.html file.

<a href="/">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
Enter fullscreen mode Exit fullscreen mode

Create the Routes

The hash routes look very similar to the URL routes above. You can reuse this part of the script. The difference is mainly how the route link key is defined.

const routes = {
    404: {
        template: "/templates/404.html",
        title: "404",
        description: "Page not found",
    },
    "/": {
        template: "/templates/index.html",
        title: "Home",
        description: "This is the home page",
    },
    about: {
        template: "/templates/about.html",
        title: "About Us",
        description: "This is the about page",
    },
    contact: {
        template: "/templates/contact.html",
        title: "Contact Us",
        description: "This is the contact page",
    },
};
Enter fullscreen mode Exit fullscreen mode

Create a function that handles the URL location

const locationHandler = async () => {
    // get the url path, replace hash with empty string
    var location = window.location.hash.replace("#", "");
    // if the path length is 0, set it to primary page route
    if (location.length == 0) {
        location = "/";
    }
    // get the route object from the routes object
    const route = routes[location] || routes["404"];
    // get the html from the template
    const html = await fetch(route.template).then((response) => response.text());
    // set the content of the content div to the html
    document.getElementById("content").innerHTML = html;
    // set the title of the document to the title of the route
    document.title = route.title;
    // set the description of the document to the description of the route
    document
        .querySelector('meta[name="description"]')
        .setAttribute("content", route.description);
};
Enter fullscreen mode Exit fullscreen mode

Finishing The Script

Again, we need to call the function when the page is first loaded, or else the home page will not work unless it is clicked on. We also need to add a watcher for the hash changes so the script knows when to show new content.

// create a function that watches the hash and calls the urlLocationHandler
window.addEventListener("hashchange", locationHandler);
// call the urlLocationHandler to load the page
locationHandler();
Enter fullscreen mode Exit fullscreen mode

Conclusion

So while there are many ways to do this, these are the 2 you need to know to make you a better frontend developer. Once you know these, you can move on to React or Vue frameworks. So hopefully these simple yet crucial learning methods helped you pass that dreaded interview question at the beginning of the article. good luck!

Read more articles on DevDrawer

Top comments (5)

Collapse
 
romaopedro199 profile image
Pedro Romão

Man, this is amazing

Collapse
 
thedevdrawer profile image
The Dev Drawer

Wow, you are quick. I am glad you like it. You can see the entire video on my YouTube channel: youtube.com/c/devdrawer

Collapse
 
ffalconet profile image
ffalconet • Edited

Hi, very useful. Thanks for that.
I am trying to do the same. How can we do to make the links 127.0.0.1:1313/contact or 127.0.0.1:1313/about or with # to be usable.
If you want to share only the link about, the user will click on it and will receive http 404 not found . How can we add to avoid that ?
Thanks

Collapse
 
wisniewski94 profile image
Wiktor Wiśniewski

Nice! Recently I have wrote article about the same thing! wiktorwisniewski.dev/blog/how-to-c...

Collapse
 
jerryi profile image
Kirill Vasin
  1. It is still valid. Thanks for using vanilla JS