Next.js - Writing universal JavaScript applications.

Server Side Rendering illustration by apptitude
Diogo Ferreira Venancio

Diogo Ferreira Venancio Developer · Full Stack April 2024

Introduction

Hi! I’m Diogo, a young software engineer working @apptitude and this article is about a trending topic in the front-end world which is “universal applications”, hybrid applications, or just simply JS apps with both client and server side rendering.

I’m going to start with short storytelling about web app architectures and patterns through time. Then, I’ll introduce the concept briefly and finally introduce you Next.js, the React universal framework we use @apptitude to create universal applications.

Even though I’m going to talk about React, the concept of universal application is not tied to it. Other libs and frameworks such as Vue (Nuxt.js) or others have also their own implementations and the principle is quite the same.

So let the journey begin!

 

The beginning

Let’s talk about PHP which the dynamic web started using in the ’90s. Back then, we used to make “Server-side” web applications, meaning that your whole HTML/CSS was generated on the server and then sent, pre-rendered to the client for it to only display that page according to the structure and styling. Each time the user completed a form or changed pages, it triggered a request to the server.

With that, you could store user data and render pages according to them on the server, but you couldn’t easily make any interaction on the client side. That meant no fancy animations, complex validations, dynamic forms, or more. JavaScript was already there but in a very limited capacity and not many people were using it.

If you wanted to make a Rich Web Application you had to use Flash, Java applets or another similar “plugin-like” tech, but it was quite heavy and it required to have the extension previously installed.

In 2006, JavaScript was about the begin a new rebirth with JQuery: you could now easily manipulate the DOM, make animations and a lot of other cool things! Pages were finally starting to be full of life and dynamic.

Content still was rendered on the server but could be easily “augmented” on the client side thanks to JS and JQuery. A few years later around 2010, the first version of Angular 1.x came out and it was one of the first JavaScript MVC frameworks with which people started to make what we call “Single page applications”.

 

What are Single page applications?

It’s basically a one-page website with all the logic, pages and routing written in JavaScript. The website feels like a desktop application and fetches his data through Ajax requests to an external API.

So a SPA has the following advantages (Non-exhaustive list):

  • Allows you to create rich internet applications (Feels like a desktop application and not anymore like a “website”).
  • Once loaded, it’s faster and transitions between pages are smoother.

But they’re really bad at (Non-exhaustive list):

  • SEO: since no HTML is received from the server, crawlers won’t be able to see much data if they don’t execute the JavaScript code.
  • Boot time: with all this code to parse and execute, apps are very slow to boot. You won’t notice the difference on a laptop but you’ll probably see a blank page in your average phone for a few seconds or when having a poor connection.
  • Pages can be heavy since we fetch all the resources we need.

One way to solve this is to use Server-side rendering, but this time, we need to render JavaScript on the server.

 

Universal JavaScript applications

So we need to make rich internet applications fully in JavaScript that load fast, are SEO friendly and not heavy. How can we do that?  We talked about Server-side rendering just before and that’s one solution. In other words, we need to:

  • Process the first request of the user on the server, meaning render the HTML/CSS, fire the Ajax requests and store the results in memory.
  • Transfer the process memory (Rendered tree, HTML/CSS and data) to the client.
  • The client will take the lead after the first request response.

Why?

The server has already parsed the code and has probably less latency than a user device. So for the first request, the client will receive pre-made data and pre-rendered HTML/CSS, thus showing content faster to the user as it doesn’t have to parse the JavaScript code to show the first paint. It also doesn’t need to trigger an Ajax request as the backend already fetched the data necessary for the current page.

Thus, we solve our boot time and SEO issues (as we receive rendered HTML that crawlers can work with) and we save the user’s bandwidth as he doesn’t need to fetch primarily data.

If you use a state management library such as Redux/Vuex/Mobx or other, you can also use it on the server and then « serialize » the data to handle it to the client. On top of that, this process needs to be as simple and transparent as possible for the developer. To summarize, universal JavaScript applications are apps that can run on both server and client seamlessly.

 

Introducing Next.js

Next.js is a React framework made by Zeit Co, a serverless cloud company. The framework makes it simple to write universal React applications and allows you to do nice things such as:

  • Automatic code splitting: Meaning loading your code from the server only when you need it. You can do it by page or by component.
  • Simple page routing
  • Static site export

What’s specific to Next.js is that every route is a page, like in simple PHP. You have a “pages” folder, and if you have the following files on it:

Items.js
Login.js
Dashboard.js

You’ll have the following routes automatically:

/items
/login
/dashboard

Each page will be automatically code split, meaning that if your user goes to the /dashboard page directly, he only fetches that page’s resources and not /items or /login.

If we take a look at the code of a page now, let’s say Dashboard.js:

function Dashboard() {

  return <div>Hello dashboard</div>;

}

Next.js will automatically render this component on the server for every first user call. You as a developer won’t need to worry about it anymore.

Most use cases require you to fetch initial data, Next.js gives you the opportunity to do this with the static method getInitialProps. Let’s say we want to fetch some alarms on the dashboard, this will be:

function Dashboard({ alarms }) {

  return <div>{alarms.map(a => a.name)}</div>;

}

Page.getInitialProps = async ({ req }) => {

  // Note that this code is the same for the server and client and can be executed in both environnements.

  const res = await api.fetchAlarams(req.authentificaedUser);

  return { alarms: res.alarms };

};


If the user types /dashboard in his browser, getInitialProps is first executed on the server and data will be fetched and rendered from there, before serving it to the user. It can then also be executed on the client, but only when the user clicks on a link to navigate to another route.

If the user clicks on a link to go to /items page, the Items.js page will be rendered on the client and his getInitialProps will be also triggered in the client instead of the server. Therefore you can write code that’s universal for your data fetching, and let Next decide if it runs it on the server or client.

 

Next.js @apptitude

At Apptitude, we started to integrate Next.js in all our JavaScript projects since September 2018, and we are pretty happy with the developer experience that it offers us to create universal applications. We use it with the following libs:

Redux and Redux-Saga
Styled-Components
Next-Routes for more complex routes

Conclusion

Universal JavaScript applications are a way to create internet applications in JavaScript that are fast, SEO-friendly and use minimal bandwidth thanks to techniques such as code & page splitting, and server-side rendering. Many implementations are available for a variety of JavaScript lib/frameworks such as React, Vue, and Angular.

Next.js is a React universal framework that allows developers to easily write universal code for React applications easily and without pain.