Introduction

As static site generation becomes more and more popular so does the need for the right tools and frameworks.

More often than not there are times when we need lightweight tools such as React to consume a decoupled service and serve static pages. There are a lot of benefits of having a tool to generate static pages for various different reasons such as speed, caching using CDN, SEO etc.

The purpose of this blog post though is to explain Next.js fundamentals - this great React framework that is growing in popularity. We will pick some of its features and will explain with examples and by doing so hopefully it will give readers a good grasp of the framework.

Assumption

As we will explain some of the Next.js features starting from beginning, the assumption is that you are familiar with React or JavaScript.

Prerequisites

Next.js requires Node.js to be installed. If you have already installed Node.js to check the version you have run node -v on your terminal and compare with the latest version Node.js - the minimum supported version is Node.js 12.22.0.

What is Next.js

Next.js is a React based framework built on top of Node.js, with Next.js you can do a wide range of things from creating APIs to consuming external/internal APIs to server side rendering, static generation and a lot more.

As we know React is a hugely popular library, see some statistics here, but it is only the view in the MVC (Model View Controller). Next.js on the other hand is framework that is built on top of React and Node.js. The React documentation makes mention of Next.js as one of recommended toolchain.

Basic Features

There are lots of features that Next.js supports out the box but here I will only focus on some of them such as:

  • TypeScript
  • Code Splitting
  • Routing
  • Static Generation
  • Data fetching

Without further ado let’s get started on creating the application.

Creating a Next.js Application

Next.js supports TypeScript out of the box, which means you don’t need to do any additional configuration.

To create we can use the following command:

npx create-next-app
# or
yarn create next-app

For those of you not familiar, npx is a package runner and CLI tool which makes it easy to install and mange dependencies hosted in npm registry. yarn is a package manager.

TypeScript

As we mentioned above TypeScript is fully supported in Next.js and to create an application that uses TypeScript you would type the following command:

npx create-next-app --ts
#or
yarn create next-app --typescript

As you can see we use --ts and --typescript flags to tell the CLI tool create-next-app to create the application using TypeScript.

Now let’s create our application by navigating to your preferred directory and type this command on your terminal:

npx create-next-app nextjs-with-typescript --ts

nextjs-with-typescript is the name of our application.

The above script will install all the necessary dependencies and when it finishes will print in the screen some commands that you can use to run the application. That’s all it is to that, you will now be able to run the application without extra configuration that you would normally need in order to compile TypeScript.

Code Splitting

Code splitting is an optimisation technique that splits the code in chunks or small bundles which then can be loaded on demand or in parallel, this way it enables the application to load a lot faster.

The importance of code splitting can be best seen on growing application, as the application grows so does the size of the JavaScript file.

Next.js Code Splitting

Next.js has built-in support for code splitting, that means you don’t have to use any external plugins such as babel.

When loading the page Next.js only loads the JavaScript necessary for that particular page. Next.js does this by analysing the resources that is importing. If for example one of your pages makes use of axios library, then that specific page will include axios in its bundle. In this way we make sure that we only send the JavaScript needed to the client.

Next.js also supports dynamic import(), this feature makes it possible to import JavaScript modules dynamically and load each import as a separate chunks. To get an understanding how that is done you can have a look at your application’s built directory which is .next.

As we mentioned earlier, when you create the application it will generate a README file which has some basic information such as how to run and build the application, for convenience, I’ll list some commands here:

# to run the application
npm dev 
# or 
yarn dev

# to build the application
npm build 
# or 
yarn build

In order to see the built folder you would need to build your application.
Once that done you can then navigate to .next directory, there you will see something like the following:

|- chunks
  |- {someNumber}.{hash}.js
  |- commons.{hash}.js
|- runtime
  |- main-{hash}.js
  |- webpack-{hash}.js
|- {hash}/pages
  |- _app.txs
  |- _error.txs
  |- index.txs

As we can see from the above, the code splitting is done by chunks, runtime and by page.

Routing

Routing is another feature that Next.js supports out of the box. Next.js uses the file system to enable routing, every file that you put under the pages directory with the extension .js, .jsx, ts or tsx automatically becomes a route.

Pages A Next.js page is a React Component, in our application that we created earlier on we are going to go ahead and create a page under pages directory called about.tsx as follows:

// file pages/about.tsx
function About(){
  return <h1>About</h1>
}
export default About

The above is a React Component that simply returns a h1 heading. Now If we run the application and go to /about route, you will see the about page that has About tag that we just created. That’s how easy it is to create a route.

Index Routes

In Next.js a file named index.ts or index.js in the root directory of any directory under the pages directory, will automatically be a route.

Here are some examples:

  • Creating an index.ts page at pages/index.ts will create a route /.
  • Creating a page at pages/blog/index.ts will create a route at /blog

Nested Routes

If we need to create a nested structure then under pages directory we would create directories and files which would then map to the routes. Here are some examples:

  • pages/articles/my-first-article.tsx will create a route /articles/my-first-article.
  • pages/admin/settings/user.tsx will create a route at /admin/settings/user

To demonstrate this we will create the following two pages in our app.

// file pages/articles/my-first-article.tsx
function MyFirstArticle(){
  return <h1>My First Article</h1>
}
export default MyFirstArticle
// file pages/admin/settings/user.tsx
function User(){
  return <h1>User</h1>
}
export default User

Now if you run the application and navigate to /articles/my-first-article or /admin/settings/user you will see the above components being served respectively.

Dynamic Routes

As explained above routes are defined based on the file and folders that we create under pages i.e. every file in there maps to a route.

Having said that, there are lots of cases in more complex applications that predefined routes are not enough and hence where the dynamic routes come in.

To create dynamic routes you can use square brackets in the name of the file like so [param].

In the following we are going to create a dynamic route so when people go to articles/<id>, the article id will be sent as a query parameter to the page, in turn we can get this article id using router query object and do further processing.

Let’s start by creating [aid].tsx file under pages/articles/ like the following:

// file pages/articles/[aid].tsx
import {useRouter} from 'next/router'

const Article = () => {
  const router = useRouter()
  const {aid} = router.query

  return <p>Article id: {aid}</p>;
}

export default Article;

The route articles/3 will be matched by [aid].tsx so now If you go to articles/3 it will display Article id: 3. The route articles/3 will have this query object {aid:'3'}. The id can be anything that serves best your needs that is to say it can be a string a number etc.

If the route has articles/3?foo=bar then router query object will have {foo:'bar', aid:'3'}, i.e. if you do console.log(router.query) you will then see the above values.

Nested Multiple Dynamic Routes

In cases where you need two levels of the route to be dynamic such as articles/3/a-comment then you would create a folder and another file under that folder. Say for example you want to capture the article id and its comment something like this http://localhost:3000/<id>/<comment> then in this case would create a directory [aid] under the pages and a TypeScript file under [aid] so then you would have this structure pages/articles/[aid]/[comment].tsx.

Then in the [comment].tsx file put the following:

// file pages/articles/[aid]/[comment].tsx
import {useRouter} from 'next/router'

const Comment = () => {
  const router = useRouter()
  const {comment} = router.query

  return <p>Comment: {comment}</p>;
}

export default Comment;

If you go to this route now articles/3/a-comment the query will have

{comment:'a-comment', aid:'3'}

As inside directory [aid] we are mapping several routes then one way to tackle this to create an index file inside [aid] directory which would match /article/<id> and another one in our case [comment].tsx to map articles/<id>/<comment>. The folder structure would then be like the below:

.
├── [aid]
│   ├── [comment].tsx
│   └── index.tsx
└── my-first-article.tsx

Catching all Routes

In cases when you want to catch all routes, then first you would create a file under the preferred directory in our case pages/[...slug] like so:

// file pages/[...slug].tsx

import {useRouter} from 'next/router'

const CatchAll = () => {
  const router = useRouter()

  return <p>This page catches all routes</p>
}

export default CatchAll;

If you now run the application and navigate to http://localhost:3000/a it will display the page that we created above.

If you navigate to http://localhost:3000/a/b and observer the query like so console.log(router.query) then you will notice that slug now has the following:

{
  "slug": [
        "a",
        "b"
    ]
}

Static Generation

One of the great features that Next.js has is static site generation. There are a lot of frameworks can generate static sites, but what makes Next.js different to other SSG frameworks is the fact that Next.js is hybrid tool which can generate HTML/CSS/JavaScript at run time as well as at build time, this and lots of other features makes Next.js a truly great React SSG framework.

Next.js has two form of pre-rendering, Static Generation and Server-side Rendering. The difference between the two is when static assets, such as HTML, JavaScript, CSS etc., are generated.

With the Static Generation HTML pages are generated at build time and after that they are reused at each request, whereas with Server-side Rendering the pages are generated at each request.

If Static Generation is used pages will be generated when you run next build and from there you can use a CDN if you want to cache the assets.

Static Generation without Data Fetching

This is a simple rendering of static pages such at the page that we have created at the beginning my-first-article.tsx

function MyFirstArticle(){
  return <h1>My First Article</h1>
}
export default MyFirstArticle

Once we created this page and build the application this page will be available as am HTML page.

Data Fetching and Static Generation

In cases where you have to fetch the data from an API or similar then you would use built-in function called getStaticProps to fetch the data at build time and then serve static pages.

If you are also catching paths then you would need to use getStaticPaths built-in function. This function can be used in addition to the getStaticProps.

Data Fetching and Static Generation Working Example

In the following will got through an example to illustrate this. Let’s say we are capturing article data from an API the way we would implement that is to first fetch the data using getStaticProps then we would have another function to consume those data.

In the following example we will use an example endpoint https://jsonplaceholder.typicode.com/posts that has random posts.

function Article({props}) {
  // Here you can further work with props and manipulate data as required
}

export async function getStaticProps() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const articles = await res.json()

  return {
    props: {
      articles,
    }
  }
}

export default Article;

In the above functions we are catching all the data from the endpoint and returning them as props, then in turn we are passing the props to the above function as a parameter where we can do further processing.

Conclusion

If you are familiar with JavaScript, Next.js is very easy to use and learn. Out of the box support for TypeScript, server-side rendering, static page generation and lots more, makes Next.js a very strong contender to use for your next project, be it a blog or a complex application that consumes an internal/external API.

Comments