Rahul Kumar (@rahul)
Discover the inner workings of Single Page Application (SPA) navigation without React, dynamic content rendering without full reloading of page, browser history api uses and more...
Single Page Application(SPA) uses client side routing to control the navigation. It allows them to render only few part of the page instead of doing full page reload. Mostly people have seen this feature in SPA frameworks, like React, Angular, etc. In this blog, we'll explore how they does SPA navigation.
Our initial setup of this tutorial has two files, one is index.html
and another is index.js
.
<html lang="en">
<head>
<title>@dsabyte - CSR</title>
<script src="index.js"></script>
</head>
<body>
<h1>@dsabyte - CSR</h1>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/user/rahul">Rahul Kumar</a></li>
<li><a href="/user/shani">Shani Kumar</a></li>
</ul>
<div id="container">
<p>Welcome to the @dsabyte CSR demo...</p>
</div>
</body>
</html>
window.addEventListener("DOMContentLoaded", () => {
console.log("CSR Page Loaded!");
});
There are a few links on the HTML page. You will be redirected to a 404 page If you click on any one of the links,
404 redirection will not work on codepen. You should try this on your local server. You can try this live server extension to run on local host.
We want to render different pages/content every time any link is clicked. But the twist is, that we can not request the server to send a new page/content every time a link is clicked.
Let's start by creating a basic router
const router = {
init() {},
go(route) {},
};
We have created a router template that has two methods, init
and go
. init
method will initialise the client-side routing, Let's write init method.
const router = {
init() {
console.log("init");
const links = document.querySelectorAll("a");
Array.from(links).forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault();
const route = link.getAttribute("href");
console.log("Route clicked: ", route);
});
});
},
go(route) {},
};
window.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded");
router.init();
});
init()
method will get all links available on the page. It will change the default behaviour by calling the preventDefault()
method. This is required otherwise the browser will redirect the user to a new page by default.
Now, we can intercept clicks to the links and we have the URL that has been clicked. It's time to render different content based on the clicked URL.
Whenever the user clicks on a new URL, we'll get the clicked URL. Now we have to change the content of the page according to the new URL. Let's modify the go()
method to change page content based on URL.
go(route) {
console.log("go", route);
const container = document.getElementById("container");
switch (route) {
case "/":
container.innerHTML = "<h1>Home Page</h1>";
break;
case "/about":
container.innerHTML = "<h1>About Page</h1>";
break;
case "/user/rahul":
container.innerHTML = "<h1>Hello, i am Rahul</h1>";
break;
case "/user/shani":
container.innerHTML = "<h1>Hello, i am Shani</h1>";
break;
default:
console.log("404 Page");
}
}
The path in the browser address bar does not change even if we click on any URL. It causes several problems for example, no feedback is given to visitors, the URL can not be shared, the URL can not be bookmarked etc.
Let's create a function that can change the path in the address bar.
function pushStateToNavigationHistory(route) {
history.pushState({ route }, "", route);
}
You can learn more about pushState
here. pushState
method will add a new entry into the browser session history stack.
Change the go method to call pushStateToNavigationHistory
upon any url clicked.
go(route) {
console.log("go", route);
pushStateToNavigationHistory(route);
// ...... other things....
},
Now, you can see that the path in the browser address bar is being changed. But, now we have another problem to solve.
Suppose initially we were at http://localhost:3000
and we clicked on a link which redirected us to http://localhost:3000/user/rahul. So far everything is fine. But as soon as you refresh the page, you'll get 404
indicating there is no such page.
The initial request is always made to the server. Once the page is loaded we can apply client-side routing. Currently, our server has only one resource http://localhost:3000. We need to modify our server to send the same HTML page on any request. Here is a simple node server that serve the same page on any request.
const express = require("express");
const path = require("path");
const app = express();
const port = 3000;
// Serve static files from the 'public' directory
app.use(express.static(path.join(__dirname)));
// Route for serving the HTML file
app.get("*", (req, res, next) => {
// Check if requested URL does not end with .js extension
// /user/rahul
if (!req.url.endsWith(".js")) {
return res.sendFile(path.join(__dirname, "index.html"));
}
// If the requested URL ends with .js extension, pass the request to the next middleware
next();
});
// Start the server
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
Instructions to execute server code will be shared at the end.
You need nodejs to be installed on your system. If you don’t have nodejs then use the following references to install it
Load http://localhost:3000
after starting the server. Now, you can navigate to any route and even you can refresh any route.
But we have another problem...
When you refresh the other than home route, you'll get the see the same content as of home route. We need to render content dynamically based on the page path after loading the page.
Modify the DOMContentLoaded
listener as follows to render content based on the current path. router.go(window.location.pathname)
will call router.go
method with the current path. router.go
method will check the path and render the content based on that path.
window.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded");
router.init();
router.go(window.location.pathname);
});
You'll be able to load the right content for current URL after making this change. But still there is an issue. You can only navigate forward, there is no way to render the previous page content by using the browser back arrow button.
popstate
is an event that gets triggered when we click on the back button in the browser. It also gives the state which was used while pushing into history. We can use that state to go back to the previous URL as follows.
window.addEventListener("popstate", function (event) {
const state = event.state;
router.go(state.route);
});
Now you can easily navigate back and forth. You can read more about the popstate
event here.
Clone repository
git clone https://github.com/ats1999/client-side-routing.git
Install NodeJs
Install Packages
cd client-side-routing
npm i
Starting Server
node server.js
Once the server is started you can visit http://localhost:3000
and happy navigation.
If you liked this blog, then please star this repository github.com/ats1999/client-side-routing
Add a thoughtful comment...
✨ Explore more tech insights and coding wonders with @dsabyte! Your journey in innovation has just begun. Keep learning, keep sharing, and let's continue to code a brighter future together. Happy exploring! 🚀❤️
Join the "News Later" community by entering your email. It's quick, it's easy, and it's your key to unlocking future tech revelations.
Weekly Updates
Every week, we curate and deliver a collection of articles, blogs and chapters directly to your inbox. Stay informed, stay inspired, and stay ahead in the fast-paced world of technology.
No spam
Rest assured, we won't clutter your inbox with unnecessary emails. No spam, only meaningful insights, and valuable content designed to elevate your tech experience.