Coding Tutorials July 2, 2021 by Konstantin Komelin

Get Off the Ground with Next.js

Intro

Based on my experience of working with different frameworks in different programming languages, I can say that Developer Experience (DX) is very important when you want to get your creative programming solutions to life. When you get excited by your new project idea, you don’t want to spend hours configuring app bundling, code-splitting or server-side rendering for your new React project. That is where Next.js framework comes in handy to reduce the amount of boilerplate you write and let you focus on delivering real value to your users.

In this brief tutorial, you will find everything you need to take off with Next. 

I assume you have some basic knowledge of React, and Visual Studio Code is installed on your machine.

Let’s start from what Next.js is about…

What is Next.js

Next.js is an open-source React framework which was first released in 2016 as a JavaScript toolchain with out-of-the-box functionality requiring no setup. Since that time, Next has become a popular production-ready framework with a huge community.

Even though Next.js has everything preconfigured for a good start, it’s flexible enough to provide the choice between NPM and Yarn, JavaScript and TypeScript, CSS and SCSS, static export and serverless deployment model.

React vs. Next.js – What’s the Difference

You probably know that React is a library and not a framework, meaning that it needs some additional building blocks for developing production-grade apps. To name a few, a router component, an app bundler and a style engine. Next.js provides those missing pieces of the puzzle but still allows to fully utilize everything people love React for.

How to Create a Next.js App

To start with Next, make sure you have Node.js > 10 installed on your machine.

The quickest way to create a Next app is to use create-next-app.

npx create-next-app first-nextjs-app

The npx installer takes care about installing create-next-app itself and creating our first Next app with all the dependencies required.

Once the installer has finished, the app can be found in the first-nextjs-app folder. Let’s run the app to make sure everything is fine with the installation:

cd first-nextjs-app
yarn dev

If everything goes well, Next should start the app in the development mode on http://localhost:3000/. If you open it, you will see a page with welcome text and some links to documentation.

Thanks to Fast Refresh technique integrated into Next, if you change anything in your code, for example a line of text in pages/index.js, the changes will get onto the page instantly without losing current app state.

As a naturally curious person, I often catch myself exploring the content of a generated project. If you’re curious enough too, let’s look at what’s inside of the first-nextjs-app folder. 

Next.js App Folder Structure

Open the project folder in VSCode:

cd first-nextjs-app
code .
Default Next.js app folder structure screenshot

Here is what we have:

yarn.lock – by default, create-next-app utilizes yarn package manager, so yarn.lock file contains information about all installed third-party dependencies. Prefer npm? Then pass –use-npm parameter to the create-next-app tool.

next.config.js – is used for Next settings primarily.

.eslintrc – eslint configuration. By default, it configures the linter to validate the code base against Next best practices and Core Web Vitals but you can extend it with additional plugins.

styles/ – for your app styles, obviously.

public/ – for static content which will be publicly available on https://your-domain/, e.g. a logo. Please be careful and don’t put any configs or code files in there.

pages/ – page routes.

pages/api – API routes.

Next doesn’t enforce how you should structure your code files. You can create components/ folder for React components and redux/ for your Redux store-related code. It’s up to you, but from my experience, it’s better to keep the first level of the project as clean as possible, so you may consider putting all your app logic in a separate top level folder.

How to Install React Developer Tools

You know that Next.js is a React framework, so you can make development more pleasant by installing the React Developer Tools plugin for your browser. To do that follow your browser-specific link and press install For Chrome For Firefox 
After installing the browser extension, the React Developer Tools icon will appear in your extensions bar and the corresponding Component tab will be added to your browser developer tools window.

React Dev Tools component tree screenshot

How to Style React Components in Next.js

Next supports a few styling approaches out of the box, such as inline styles, standard CSS file includes and CSS modules.

Inline styles

<p style={{ color: 'red', fontWeight: "bold" }}>red text</p>

Note that the properties of the inline styles should be camelCased, so we use fontWeight instead of font-weight in this particular case.

CSS file include

/* styles.css */
.red {
     color: red;
}

/* component.jsx */
import "styles.css";

<div className="red">Red</div>

CSS Modules

To use CSS file as a module, name it *.module.css

/* component.module.css */
.red {
     color: red;
}

/* component.jsx */
import classes from "component.module.css";

<div className={classes.red}>Red</div>

With this approach, the entire CSS file is treated as an object where every class in the file has its corresponding object property.

Read more about how to use CSS Modules in its documentation on Github.
If you prefer the SASS preprocessor for structuring styles, Next provides seamless integration with sass. To use it, just install sass and give your style file .scss or .sass extension.

yarn add -D sass

It’s also possible to use the CSS Modules approach with SASS. And since Next.js is React-based, you can use any CSS-in-JS approach by installing corresponding packages.

Next.js Routing System

Scripts which are located in the pages/ folder of the app are converted into routes automatically. There are two types of routes in Next, such as page routes and API routes. Let’s try both of them.

Page routes

Create pages/time.js with the following content:

import React from 'react';

export default function Time() {
  return (
    <div style={{
      display: 'flex', 
      flexDirection: 'column', 
      justifyContent: 'center', 
      alignItems: 'center', 
      height: '100vh'
    }}>
      Time: {(new Date()).toLocaleTimeString()}
    </div>
  )
}

If you then open the http://localhost:3000/time page you will see current time in the page center.

API routes

Create the time.js file in the pages/api/ folder:

export default function handler(req, res) {
  res.status(200).json({ 
      time: (new Date()).toLocaleTimeString() }
  );
}

After running the app in the development mode, the created API handler will become available on http://localhost:300/api/time and will respond with JSON content like this:

{"time":"8:43:48 AM"}

API handlers are isolated but they have access to the request and response objects.

Next.js Application Configuration

There are two officially supported ways to store app configuration in Next, such as runtime configuration with next.config.js and build-time configuration with .env files.

Runtime configuration

The runtime configuration is primarily used for storing Next.js own settings. Even though it’s possible to use it for app settings too, it’s not recommended because the runtime configuration affects performance and prevents the page from being automatically statically optimized.

Built-in time configuration

In a simple case scenario, it’s enough to have a single .env.local configuration file for production and development environments with variables defined the following way:

# .env.local
APP_DOMAIN="example.com"
APP_URL="https://${APP_DOMAIN}/"

As you may see, we can use previously defined variables for forming new variables.

Then all environment variables can be accessed from the code this way:

console.log(process.env.APP_URL);

Don’t forget to include your .env.local file to .gitignore to keep your passwords and other private configuration in secret.
But .env.local is not the only possible config file. Below is a list of config files which you may want to use in more complex scenarios.

FilePurposeOverrides
.envEnvironment-independent defaults, e.g. DATABASE_TYPE=”mysql”nothing
.env.developmentDevelopment-only configuration, used by yarn dev.env
.env.productionProduction-only configuration, used by yarn start.env
.env.testTest-only configuration, used by test engines, such as Jest, Cypress, etc..env
.env.localThe main app configuration which is not stored in Git and is loaded for every environment except the test environment..env
.env.development
.env.production

One important thing to keep in mind is that by default, your environment variables are only available from server-side code. To utilize a variable on frontend, prefix it with NEXT_PUBLIC_, for example:

NEXT_PUBLIC_APP_URL="https://example.com"

How to Implement Dynamic Imports in Next.js

Dynamic import() of JavaScript modules allows you to split bundled code into a few smaller chunks and load them on-demand (aka lazy-loading).

Here is an example where our crypto module provides the encrypt() function which we load dynamically to encrypt the password:

import("../lib/crypto").then(({encrypt}) => {
  console.log(encrypt("PASSWORD"));
});

And what if we need to load a React component dynamically and not just a module with some business logic? For this purpose, React itself has a solution, called React.lazy(). Unfortunately, it doesn’t support server-side rendering, so we can’t use it with Next.

Good news is that Next has its own solution for dynamic import of React components – next/dynamic.
To see how dynamic imports work in Next, let’s create a component in the components/ folder of our Next app with the following content:

export default function DynamicComponent() {
  return (
    <div>Loaded dynamically</div>
  )
}

Now we can use the created component from a page like this:

import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(
  () => import('../components/DynamicComponent')
);

export default function Home() {
  return (
    <div>
      <DynamicComponent />
    </div>
  )
}

Easy, right? But let’s imagine a situation when you found a beautiful image gallery component for your app which doesn’t support server-side rendering. In that case you may want to load the component on client only by passing ssr: false as a dynamic() function parameter:

const DynamicComponent = dynamic(
  () => import('../components/DynamicComponent'),
  { ssr: false }
);

How to Deploy Next.js App

Static app

Next makes it easy to generate fully-static sites which can be hosted anywhere without the need for a Node engine.
All you need to do is add export command to your package.json scripts:

"scripts": {
  "export": "next build && next export"
}

And run:

yarn export

Your static app will be generated in the out/ directory of your project. Just deploy the folder content to any static hosting or CDN and that’s it.

The problem with static exports is that if your Next app has data requirements, for example it loads blog content from a remote third-party source and visualizes it someway, your static deploy will be based on the current version of the data. You have to re-export your app regularly to reflect the data changes. 

Please note that API routes are not supported here.

Standalone app with PM2

Alternatively, you can keep your app dynamic and run it as a standalone Node.js app, for example on port 80:

next build && next start -p 80

This way you have to restart the app on errors manually and handle server restarts which is not a very convenient approach. Instead, I suggest using a Node process manager, for example pm2, to solve these problems.

Standalone app with Docker

If you enjoy using Docker for hosting your apps as many of us do, follow the with-docker example from the official examples. You will find a multi-stage Dockerfile there which is tuned for Next.js apps.

Serveless app

For those who build serverless apps, starting from version 8, Next supports a serverless deployment model where each page in the pages/ folder is converted into a standalone lambda function.
To enable the serverless mode in Next.js, add the serverless build target through your next.config.js:

// next.config.js
module.exports = {
  target: 'serverless'
}

To Sum Up

As you may see, Next.js simplifies development of React apps and helps focusing on the most important things – your app logic and UI. It has everything which is necessary for building modern frontend-rich and API-powered apps. And because of its ability to generate static HTML sites, it’s also suitable for content-only projects, such as blogs and corporate sites.

With the latest versions, Next.js not only keeps the developer experience at a high level but it also provides tools for improving visual performance and user experience, which, no doubt, secures a great future ahead for this framework.

Next Steps

Nothing works better than practice, so I recommend you come up with a simple project idea and get your hands dirty with Next to cement what you’ve already learned.

In case you need some additional resources, refer to the Next.js documentation and Next.js examples.
And if you’re looking for a higher level framework, you may be interested in giving Blitz a try because it’s inspired by Ruby on Rails and built on top of Next.js.

One developer subscribed to the Uploadcare blog and eventually became a CTO