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
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>
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>
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();
});
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",
},
};
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();
};
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);
};
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();
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>
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",
},
};
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);
};
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();
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)
Man, this is amazing
Wow, you are quick. I am glad you like it. You can see the entire video on my YouTube channel: youtube.com/c/devdrawer
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
Nice! Recently I have wrote article about the same thing! wiktorwisniewski.dev/blog/how-to-c...