BLOG

Next Js adding a loading screen — How I ended up not using a library


Next Js is one of the best documented, easy to pick up Js framework out in the huge community. Quite recently, I migrated my portfolio from React — Client Side Rendering to Next’s awesome SSR. The entire process of making the shift took me no more than 6–7 hours.

Next Js - A React Framework
Next Js - A React Framework

While working on the portfolio (which has the basic couple of screens coupled together in bad UX), I decided to add a loading screen for my user to wait to over his( much hated ) slow internet connection while he/she waited for the next page to show up.

Why Next Js desperately needs a loading screen

Next Js provides an easy to use Link component which enables client-side transitions between routes. But despite all its advantages, it has the potential to be a major UX disaster.On clicking a <Link>...</Link> without any warning or signal, the page seemingly freezes while the client waits for the response for the next ‘‘Page’’. After fetching the response, the screen changes abruptly to the new page. For a user, this could be a signal to drop off and potentially never return after 2–3 attempts.

Next Js Link Janks while waiting for page to load
Next Js Link Janks while waiting for page to load

I did a quick Google search around this which showed me a couple of libraries, in a couple of articles and stackoverflow answers, that got the job done.

Seemed super easy! And they really did work.

The code at the end in _app.js looked something like this —

import React from 'react';// import next's router for callbacksimport Router from 'next/router';import App, { Container } from 'next/app';
// some popular library that does thisimport ?? from '??';
// start:Some library configuration...// :end
Router.onRouteChangeStart = (url) => { // Some page has started loading ??library.showLoader();};
Router.onRouteChangeComplete = (url) => { // Some page has finished loading ??library.hideLoader();};
Router.onRouteChangeError = (err, url) => { // an error occurred.};
export default class MyApp extends App

With great comfort, the Next Js Router object also exposes -

  • beforeHistoryChange(url) - Fires just before changing the browser's history
  • hashChangeStart(url) - Fires when the hash will change but not the page
  • hashChangeComplete(url) - Fires when the hash has changed but not the page

Reality check. What did the library really do?

Probably the following steps —

  • Add a loading element to document body ( or perhaps some other main element ).
  • Some transition to display the loading element on screen.
  • Some transition to remove the loading element from screen.

So here’s what I came up with —

  • Create a React component which renders my loader.
  • Add CSS to support the transitions into and out of the screen.
  • Expose props to control the CSS class on the component.

At the end of the efforts, the code looked something like this in _app.js —

import React from 'react';// import next's router for callbacksimport Router from 'next/router';import App, { Container } from 'next/app';
// A simple component that we createdimport Loader from '@/components/Loader';
export default class MyApp extends App {
constructor() {
Router.onRouteChangeStart = (url) => { // Some page has started loading // Set state to pass to loader props this.setState({...}); };
Router.onRouteChangeComplete = (url) => { // Some page has finished loading // Set state to pass to loader props this.setState({...}); };
Router.onRouteChangeError = (err, url) => { // an error occurred. // some custom error logic. };
};
render() { <Loader {...someProps} /> ... }
...}

So what changed here? Nothing really. It took approximately the same amount of brainstorming that it had taken before. The only new information that I really needed was about the callbacks that can be intercepted from Next Router. Most libraries add the loader directly into the document body. Doing this setup also allows us to reuse the loader inside other smaller components.

All that got me was a little more granular control over my loading screen and shaved off a couple of Kbs off my final bundle in almost the same amount of effort it had taken me to have the library setup ( less if you take the time taken for deciding the library into account ).
Loading screen added to fix the UX
Loading screen added to fix the UX

Some stuff that I added on later —

  • Adding a debounce to avoid the loading screen for low load times.
  • Lots of playing around with the animations.
  • Added a fake loading timer so that the loader screen is present for a minimal amount of time after is it invoked.
  • Consuming the other router callbacks for better control over my loader.
  • Encapsulating everything inside the loader so that it can be reused anywhere without any repeated configuration.

Hi! I'm a web developer making assets on the internet. Think you have something to say about the article? Do reach out to me at saranshgupta1995@gmail.com