How we built a faster Toppr web app using React and Redux

Gautam Chaudhary
Toppr Blog
Published in
9 min readOct 31, 2017

--

In August, we launched a revamped version of Toppr.com and gave you a low down on our design journey and product decisions. Today, I’m going to take you into the reasoning of various architectural decisions that helped us build this new single page app (SPA).

Why create a Single-Page Application (SPA)?

A single-page application (SPA) is an application that works inside a browser. SPAs load a single HTML page and all the necessary assets (such as JavaScript, CSS and Fonts) required for the application to run only once throughout the lifespan of the session. Any interactions with the page or subsequent pages does not require a round trip to the server, which means the page is not reloaded. The SPA requests the data directly from the server using Ajax and renders the page straight in the browser using JavaScript. If built in the right way, SPAs are not only faster than a traditional website, but also provide a better user experience.

We understand the importance of providing a fast and delightful experience to our users, and hence decided to build the next version of Toppr.com as a single page app.

Decoupling from Django

Our existing web app was built on a custom JavaScript microframework based on jQuery, and was tightly coupled with our backend Django server. All of our product pages were rendered on the server using Django templates, and then JavaScript was used on the client for handling the UI interactions. While this had been working well for us so far, we knew that in order to build an interactive and high performance web app, we would need to decouple the client layer from the data API layer. It was not only making it difficult for us to implement new features quickly, but also restricting us from providing a better user experience. As we embarked on this redesign journey, we decided to grab this opportunity with both hands. Alongside the redesign, we took on the task of rewriting our web from scratch using a modern frontend stack.

Choosing the right framework

The most crucial decision while building a web app is deciding on a framework or library to use. There are a multitude of options available today like Angular, React, Vue.js, Ember.js, Meteor, etc, and narrowing down to one of these depends on various factors like project requirements, company goals, team size, expertise, etc. We have been using React and Redux in many of our internal systems and have been impressed with the performance and development speed they provide. Some of the reasons as to why they work really well for us are:

  • React makes it easy for us to build reusable user interface components
  • React gives us high rendering performance in the browser with its Virtual DOM diffing algorithm
  • They allow us to render the pages on the server, and reuse the server state in the client
  • Both React and Redux provide great Developer tools which make it much easier to inspect the components within their hierarchy. They help us look into their props and state, observe the dispatched actions and watch changes in the store.
  • Redux time travel feature allows us to record and go back to any state of the app for debug purposes.
  • Both of them have amazing developer communities, well-written docs, and tons of free resources available online.

Considering all the advantages, we decided to go with React and Redux for our Toppr web app as well.

Our new stack

The Toppr React web app is written in ES2015+, and is transpiled to ES5 using Babel. We used Webpack as our bundler for it’s incredible support for loaders, plugins, hot module replacement, code splitting, and tree-shaking. We used Redux for state management due to it’s simplicity and benefits like unidirectional data flow, hot reloading, server-side rendering, etc. For the routing, we decided to go with React Router 4 for its ability to do dynamic route matching and lazy code loading.

Using CSS with React

While we have been following the BEM convention for naming our CSS classes in our existing web app, we decided to use css-loader module in our new web app for it’s ability to generate unique class identifiers during the build. In order to ensure that all of our React components are reusable, and that we can eliminate dead CSS code without worrying about it’s usage across the code base, we created a style file for every React component, gave it the same name as the component, and also placed it alongside the component. Now, if we are to ever stop using a component, the related CSS code will automatically get excluded from the bundle and will not bloat our CSS output.

While styled-components have been gaining traction recently, we decided to use Sass for writing the style code as we found that styled-components are not yet stable and mature enough for our requirements. We compiled our Sass code to CSS using sass-loader, added vendor prefixes by using Autoprefixer along with postcss-loader, and generated the CSS output using css-loader. We also used url-loader for inlining some of the images within the CSS file.

Using SVGs for Icons

Since icons are an essential part of our new vibrant design, and many of the icons have been reused across the product in different size and colors, we did some research to find the best solution for using icons in our web app.

We needed an icon system that met the following requirements:

  • Ability to resize without losing quality.
  • Ability to change the color and styling using CSS.
  • Cross-browser compatibility.
  • Minimum network requests.

We explored various approaches for using icons in web, and narrowed it down to the following:

  1. Image Sprites — This is a very popular technique in which multiple images are combined together into a single file to reduce network requests. While this approach reduces the network requests, the icons can’t be resized or styled as they are represented using bitmap images. Also, it can increase the load time when used with a large number of icons.
  2. Font Icons — Although these solve the problems of Image sprites to some extent, as they are vectors and can be easily resized and colored using CSS, they come with their own challenges like inconsistent rendering across browsers, difficulty in positioning, hard to detect fail state, etc. Also, implementing multi-color icons using fonts can be very challenging.
  3. SVG Icons — Using SVG for icons seemed like the best option to us as they not only solved the challenges that we faced with Image sprites and Font icons, but also gave us additional features like the ability to animate layers within the icon, etc.

After during further research, we learnt that there are 3 popular ways of using SVG icons in web, and they all have their own pros and cons.

  • Background image in CSS — Including SVG image in the CSS as a background image is the easiest way to use SVGs in web. This approach makes it easy to resize the icons using the background-size CSS property. But the drawback of this approach is that we cannot control the innards of the SVG using CSS, and hence cannot change the color. Also, if we were to inline the icons within the CSS file as data URI, we’ll end up with duplicate code for every usage of the same icon in our codebase.
  • <img> tag — In this approach the SVG file is used just like any other image in the HTML by providing the icon url in the <img> tag. While this solves the problem of duplicate code in the CSS, we still can’t change the color of the SVG elements.
  • Inline SVG — As the name implies, we simply drop the entire code of the SVG file in our HTML and it gets displayed same as any other image. This approach gives us the ability to completely modify the SVG image elements using CSS and also reduces the extra network requests as the icon is a part of the HTML document.
import React, { Component } from 'react';
import { AccuracyIcon } from ‘toppr-icons’;
class AccuracyStat extends Component {
render() {
return (
<div>
<AccuracyIcon/>
{this.props.value}
</div>
);
}
}

Since converting every SVG icon into a React Component would have become cumbersome, inspired by the popular react-icons package, we decided to build a custom tool for doing the heavy lifting and automatically generating React icon components. We have since open sourced this tool as react-svg-components-generator.

Delivering high performance images

Images are an integral part of our product and are used for displaying diagrams in questions and concepts, photos in doubts on chat, thumbnails in videos, and profile photos of our users. We used Cloudflare and CloudFront CDN for faster fetching of images, and used Cloudinary for on-the-fly resizing of images based on device dimension and screen density.

Serving custom fonts

Generally it makes sense to use custom fonts from Google Fonts, so as to take advantage of the fast and reliable Google CDN. However, there are few disadvantages of this approach:

  • Extra request for resolving the DNS and fetching the Google font CSS file from fonts.googleapis.com
  • Additional requests for resolving the DNS and downloading the font files from a different host fonts.gstatic.com
  • Less control over the caching of font files
  • Not taking advantage of single HTTP/2 request

Since Google Fonts does not provide us an option to download the individual font files, we used the free google-webfonts-helper tool to download the fonts used by us, and served them using our own CDN. This minimized the DNS lookups, and improved the page load time by taking advantage of parallelism in HTTP/2.

Skeleton Screens

The art of engineering and UI is to make your user’s journey easy, quick and attractive. However, at some point your user will have to wait for something to load. The easiest way to tackle this problem is to display a loader, but there is a huge con here that we weren’t willing to take on. Our users getting bored and mistakenly assuming the app is slow. So instead we decided to do something that’s recently taken the internet by storm, skeleton screens.

When the user clicks on a link or button, the skeleton screen gives a visual cue that content is loading for the subsequent page. This approach cuts down the feeling of a longer load time and works well for the user, which in turn makes us really happy!

Modules used

Some of the other modules that we used in our web app, which might help you build your own:
axios — For making Promise based network requests.
react-modal — For displaying modal dialogs.
react-tooltip — For displaying tooltips.
redux-actions — A helper utility for creating and handling redux actions.
redux-promisePromise middleware for Redux.
moment.js — For date parsing and manipulation.
lodash — As a JavaScript utility library.
socket.io-client — For realtime communication with our WebSocket server.
classnames — For conditionally joining CSS classNames together in JSX.

What’s next

Stay tuned for more awesome content on Service Worker, Code splitting, Server side rendering and much more!

If you happen to find this article insightful, please share it for others to find.

We publish regularly on everything related to education and technology. Follow us for more.

Building the all-new Toppr iOS apps — Part 1 ← P R E V I O U S

N E X T → Building the all-new Toppr iOS apps — Part 2

--

--