Book Image

Next.js Quick Start Guide

By : Kirill Konshin
Book Image

Next.js Quick Start Guide

By: Kirill Konshin

Overview of this book

Next.js is a powerful addition to the ever-growing and dynamic JavaScript world. Built on top of React, Webpack, and Babel, it is a minimalistic framework for server-rendered universal JavaScript applications. This book will show you the best practices for building sites using Next. js, enabling you to build SEO-friendly and superfast websites. This book will guide you from building a simple single page app to a scalable and reliable client-server infrastructure. You will explore code sharing between client and server, universal modules, and server-side rendering. The book will take you through the core Next.js concepts that everyone is talking about – hot reloading, code splitting, routing, server rendering, transpilation, CSS isolation, and more. You will learn ways of implementing them in order to create your own universal JavaScript application. You will walk through the building and deployment stages of your applications with the JSON API,customizing the confguration, error handling,data fetching, deploying to production, and authentication.
Table of Contents (9 chapters)

What is a single-page app?

A single-page app implements this architecture for web clients: the JavaScript app launches from a web page and then runs entirely in the browser. All visual changes on the website happen as a reaction to user actions and the data received from the remote APIs.

It is called single-page because the server does not render pages for the client; it always delivers the same minimalistic markup required to bootstrap the JS app. All page rendering and navigation happens purely on the client, using JavaScript, which utilizes History APIs to dynamically swap page contents and URLs in the location bar.

The advantages that this approach gives are that the client can run something in the background between page transitions, and the client does not have to re-download and re-render the entire page in order to swap only the main content. Unfortunately, it also brings drawbacks, because now the client is responsible for all state changes. For the synchronization of such changes across the entire interface, it must know when to load the data and what particular data. In other words, a server-generated app is conceptually a way simpler thing, thanks to the REST service + JS client.

Creating JS Modules, code sharing, code splitting, and bundling

Separation of concerns is one of the key principles in software design, and since each entity in the code has to be isolated from others, it makes sense to put them into separate files to simplify the navigation and ensure isolation.

Modern JS applications consist of modules that can have exports and imports. JS modules export some entities and may consume exported entities from other modules.

In this book, we will use the latest JS syntax with classes, arrow functions, spread operators, and so on. If you are not familiar with this syntax, you can always refer to it here: http://exploringjs.com.

The simplest JS module looks like this:

// A.js:
export const noop = () => {};

This file now has a named export, noop, which is an arrow function that does nothing.

Now in B.js, we can import a function from the A.js file:

//B.js:
import {noop} from "./A.js";
noop();

In the real world, dependencies are much more complex and modules can export dozens of entities and import dozens of other modules, including those from NPM. The module system in JS allows us to statically trace all dependencies and figure out ways to optimize them.

If the client downloads all JS in a straightforward way (for example, initially downloading one JS file, parsing its dependencies, and recursively downloading them and their deps), then load time will be dramatic, first of all because network interaction takes time. Secondly, because parsing also takes time. Simultaneous connections are often limited by browser (the amount of HTTP threads) and HTTP 2.0, which allows us to transfer many files through one connection, is not yet available everywhere, so it makes sense to bundle all assets into one big bundle and deliver them all at once.

In order to do this, we can use a bundler like Webpack or Rollup. These bundlers are capable of tracing all dependencies starting from the initial module up to leaf ones and packing those modules together in a single bundle. Also, if configured, they allow us to minify the bundle using UglifyJS or any other compressor; this reduces the bundle size dramatically. Minification is a process where all unnecessary things are stripped out of the bundle, such as whitespaces and comments, all variables are named a, b, and so on, and all syntax constructions are simplified. After minification, we can also gzip the output if the server and client allow this.

But the bundle approach also have caveats. A bundle may contain things that are not required to render a particular requested page. Basically, the client can download a huge initial bundle but in fact need only 30-40% of it.

Modern bundlers allow us to split the app into smaller chunks and progressively load them on demand. In order to create a code split point, we can use the dynamic import syntax:

//B.js:
import('./A.js').then(({noop}) => {
noop();
});

Now, the build tool can see that certain modules should not be included in the initial chunk and can be loaded on demand. But on the other hand, if those chunks are too granular, we will return to the starting point with tons of small files.

Unfortunately, if chunks are less granular, then most likely they will have some modules included in more than one chunk. Those common modules (primarily the ones installed from NPM) could be moved to so-called common chunks. The goal is to find an optimal balance between initial bundle size, common chunk size, and the size of code-split chunks. Webpack (or other bundlers such as Parcel or Rollup) can optimize it a bit, but a certain amount of manual tuning is required for best results.