Client side routing: Let’s understand it by building our own routing library
With the emergence in Single page applications, devs have moved away from server side routing in favour of client side routing. It is quite easy to implement in popular frameworks like Angular and UI libraries like React, thanks to well established modules like ui-router, React-router etc.
In this article we are going to explore what is client side routing and how can we incorporate it in a vanilla JS web app by building our own routing module.
Disclaimer: Don’t use it in prod. Not only this module will have less features than established libraries, it will have some security issues as well.
First thing first: What is Routing?
Routing in context of web dev is to route our web requests (specified by url) to a specific code which will handle the request.
For eg: ‘myapp.com/public/home.html’ request would load home.html from public folder.
Server side routing:
In server side routing, a fresh page is retrieved every time with a new request. This causes reload of full web page and existing document is discarded, which is not ideal for a modern complex app where usually only a section of web app needs to be modified with a change in route.
Client side routing:
Now that we are done with the explanations, let’s build a small app and implement client side routing in it from scratch.
- Load initial state represented by a route
- Navigate web app using links without full reload of the app
- Go back and forward to correct web app state when done using browser’s back/forward buttons
Our app will be a simple one and main focus would be given on building and integrating client side routing in it.
index.html is pretty straight forward. We have three buttons which will be used for navigating the web app. A div with “content” id is a main placeholder where a view will be rendered according to the state of app.
We will use live-reload module to serve our application. Install it using
npm i -g live-server
and then start it using
live-server — entry-file=index.html .
— entry-file option will make sure that in case of an invalid route it will return back the index.html when the app is loaded first time.
Now let’s move on to router.js
Main method that we’ll focus on is goTo which is doing most of the work. Here’s what’s going inside it:
- Get template for the given route from registered routes map. This is usually not this straightforward in a standard routing library where there’s generally regex based match making to support nested and parametrised routes. But for the sake of this article i’ve kept it simple.
- If no template is registered for given route, fallback to a default one and cancel the current routing process.
- Render template in app - Here we get the template that was retrieved from route map and insert it in dedicated content placeholder using
document.getElementById(“content”).innerHtml = template;
We update the document title as well with the route change .
- Update the new state in history.
Let’s talk about the last step in a bit more detail.
In last step we use History API to update the url without making a server call as well as to make sure that back/forward buttons in browser work as intended.
There are mainly two history API uses to make it all work.
By using this method we can push a state to browser’s history object. Signature for this is as history.pushState(state, title [, url]).
- state can be any data be it your template or page scroll position etc.
- title is generally ignored by most of the browsers but can be used in future.
- url is the path with which you want the state to be associated with. In our case this will be the current route.
This is an event fired by history api whenever there’s a change in history state (usually done using browser’s back/forward button). We listen to this event in our app to initiate the navigation to correct route.
Next is app.js
Here we are creating an instance of Router and registering all of the routes with it, including default route as well. Next we ask router to resolve current route and navigate accordingly. In the end we register click event listeners to buttons for navigations purposes.
Our app is ready now. If you run it and navigate through app buttons or browser’s back/forward button, you’ll see that routing is working as expected and it’s not making any server call or reloading the full page.
Now there are many features that are missing when compared to full-fledged routing library but i’d like to discuss two of them.
- Using buttons for navigation — This is generally a bad practice as anchor tags are semantically more correct and won’t cause accessibility issues. I used buttons because anchor tags will make a server request by default when clicked, so you have to manually intercept and prevent that. This is usually done by either providing your own component which wraps an anchor tag internally and will override the default behaviour (See Link component in case of React-router for example.) or by intercepting all DOM click events and preventing all of those which originated from an anchor tag.
- Child routes and parameterised routes — Our router is very basic and doesn’t handle more complex routes like ‘/products/:productId’ which will take you to a page with details specific to a product id.
Take two of the above points as an exercise and try to implement them. In addition to it, you can try to build your own react router as well.
Cheers and happy coding!