What is NextJS? A Hands-On Introduction to Developing Faster Websites with Next

Intro to building full stack applications with JavaScript and NextJS
Feature Image

Even though modern Javascript-based frontend frameworks like React help us develop robust dynamic websites, they have one major downside: the client has to wait until all Javascript code loads to render the page and display content to a user. As a page’s loading speed significantly impacts its user experience and search engine rankings, this has become a major issue in web development

Next.js is a React-based framework that provides a solution to this problem. It promises to improve the performance of a website using an easy-to-use set of features, including pre-rendering. Considering how the developer community has flocked around the framework within a short time since its release, it’s safe to say that Next delivers on this promise without reservation.

Therefore, we decided to introduce you to Next.js, the framework that’s fast becoming a developer favorite, in this tutorial. Let’s understand how Next improves upon React with increased performance and a simplified development process.


Main Features of Next.js

The most prominent feature of Next.js, compared to a framework like React, is pre-rendering. As discussed above, rendering a web page on the client-side after receiving Javascript code is a slow process. Next solves this problem by sending a pre-rendered version of each page to the client.

It uses three types of pre-rendering. They are:

  • Static site generation
  • Server-side rendering
  • Incremental static regeneration

While static site generation is best for building static web pages, server-side rendering suits pages with dynamic content. The specialty of Next is that it allows you to decide which type of rendering to use for each web page separately.

Other than pre-rendering, Next provides an elaborative set of features to simplify the development process as listed below.

  • Automatic bundling and code splitting: Unlike in React, where you have to set up Webpack to bundle the code manually, Next.js automatically bundles the code under the hood using Webpack. It also supports code splitting based on separate routes and dynamic imports. It reduces the loading time of web pages and dynamic components. = Image optimization: Next natively supports on-demand resizing and optimizing of images. You can pair it with the default lazy loading option for the best performance.
  • Fast refresh: instantly re-render a component when its code changes during development.

Getting Started with Next

To get started with Next, you should have Node.js and npm installed on your device. Once the installation is complete, you can continue to build a new Next application.


With create-next-app

Next provides a create-next-app tool, similar to create-react-app, to quickly set up the project folders and environment. Here’s how you can invoke the create-next-app operation.

npx create-next-app

Once you add the application name as prompted, the command will create a project folder and install the necessary packages (next, react, and react-dom). It also sets up the package.json file with the following content:

{
  "name": "first-next-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "11.0.1",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "eslint": "7.30.0",
    "eslint-config-next": "11.0.1"
  }
}

Manually Set Up Next App

You can also set up a Next app manually, without the help of create-next-app. For this, first, create a project directory and initialize it with npm init. Then, install the necessary packages using the following npm command.

npm install next, react, react-dom

Update the script section of the package.json with the following.

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
  },
  • The dev script runs the app in development mode.
  • The start script runs the app in production mode.
  • The build script compiles and builds the app.

We’ll assume a manually set up project throughout this tutorial to help you understand the background details of creating a Next app.


Add Pages to a Next App

In Next.js, a page is a file stored in a directory named “pages.” Each page associates with a route in the web application and exports a React component for that particular route.

For example, a page with the file path pages/products/display.js should store the React component the /products/display route renders for visitors. A page with the name pages/products/index.js associates with the route /products.

Therefore, to start adding pages to the web app, you should first create a directory named pages. Then, you should add an index.js file to the directory to associate with the / route of the web app.

On our first page, we can export a simple component that displays “Hello World” to visitors, as shown below.

const Home = () => (
  <div>
    <h1>Hello World!</h1>
  </div>
);

export default Home;

Now, we can start the Next app in development mode using the following command.

npm run dev

It builds and starts our application on port 3000. You can visit its home page using the URL http://localhost:3000/.

Our simple hello world web application

Our simple hello world web application

Following a similar pattern, you can add more pages to the application.

Next provides a React component named “Link” to ensure the optimal transition between linked pages. Even though it’s possible to use regular anchor tags to connect pages, the Next’s Link component speeds up the operation by preventing the client from reimporting bundles common to both pages.

To demonstrate the linking feature, first, we need to add another page to our web app. For this, we will create a new about.js file in the pages directory and add the following code to it.

//about.js

const About = () => (
    <div>
        <h3>About Us</h3>
        <p>We strive to provide the best services to you</p>
    </div>
);

export default About;

Once you save the changes, Next automatically rebuilds the application with the new component. You can visit the page using the URL http://localhost:3000/about.

Simple about us page

Simple about us page

Now, we should provide a link to the about page on the website’s home page. Here’s how we can achieve this.

import Link from "next/link";

const Home = () => (
  <div>
    <h1>Hello World!</h1>
    <Link href="/about"><a>Learn more about us</a></Link>
  </div>
);

export default Home;
Example of link

Example of link


Dynamic Linking

What if you want to link a page using a dynamic route? Next makes this task quite simple with the help of dynamic page names and a router.

Remember how Next uses page names as routes in the web application? In the same way, Next allows us to create pages with dynamic names that relate to dynamic routes.

For example, consider a route like /shows/[tvshow]. This dynamic route changes the URL depending on the TV show (e.g: /shows/game-of-thrones, shows/friends).

Next allows us to create a common template for all TV shows in this route. We call them dynamic pages. The name of a dynamic page follows this format: pages/shows/[tvshow].js.

Then, we can use the Next router to extract the information in the dynamic URL and render the page to suit each TV show.

Let’s start this implementation by creating a page with the name pages/shows/[tvshow].js. In this file, we should create a router object to retrieve the URL parameters. Then, we can return the component with the information relevant to this parameter.

import {useRouter} from "next/router";

const TVShow = () => {
    const router = useRouter();
    const tvshow = router.query.tvshow;

    return (
        <div>
            <h3>{tvshow}</h3>
        </div>
    );
}

export default TVShow

Now that we’ve set up the dynamic page content, we can provide links to the different TV shows on the website’s homepage.

import Link from "next/link";

const Home = () => (
  <div>
    <h1>Hello World!</h1>
    <p>Popular TV Shows</p>
    <ul>
      <li>
        <Link href={`/shows/game-of-thrones`}><a>Game of Thrones</a></Link>
      </li>
      <li>
        <Link href={`/shows/friends`}><a>Friends</a></Link>
      </li>
      <li>
        <Link href={`/shows/westworld`}><a>Westworld</a></Link>
      </li>
    </ul>
    <Link href="/about"><a>Learn more about us</a></Link>
  </div>
);

export default Home;

Now our home page displays the list of TV shows with links connecting them to the dynamic route:

Hardcoded shows

Hardcoded shows

When we click on one of the links, it renders the [tvshow].js page with content relevant to each TV show.

In the next section, let’s see how we can fetch data from a remote API to display more TV show data.


Fetching Data

Next provides three different methods to fetch data from a remote API. They are getStaticProps, getStaticPaths, and getServerSideProps.

Let’s understand the differences of each data fetching method by retrieving TV show data from the TV Maze API .

getStaticProps

This function should be used on statically generated pages because it fetches data during the build time. When we declare an async getStaticProps function on the same page as the React component, it receives the retrieved data as property.

Let’s use this method to retrieve a list of recent TV shows from TV Maze API to display on the home page.

//index.js

 export async function getStaticProps() {
  const res = await fetch("https://api.tvmaze.com/shows?page=1");
  const data = await res.json();
  return { props: { data } }
}

The Home component can now use the returned data to display the list of TV show names. Here’s how we can modify the component to achieve this.

//index.js

import Link from "next/link";

const Home = ({data}) => (
  <div>
    <h1>Hello World!</h1>
    <p>Popular TV Shows</p>
    <ul>
      {data.map(tvshow => {
        return (
          <li>
            <Link href={`/shows/${tvshow.id}`}><a>{tvshow.name}</a></Link>
          </li>
        );
      })} 
    </ul>
    <Link href="/about"><a>Learn more about us</a></Link>
  </div>
);

export async function getStaticProps() {
  const res = await fetch("https://api.tvmaze.com/shows?page=1");
  const data = await res.json();
  return { props: { data } }
}

export default Home;

The dynamic link to each TV show now uses the show ID instead of its name shows/[tvshow-id]. And our home page now looks like this:

Links dynamically generated from shows API

Links dynamically generated from shows API

getStaticPaths

getStaticPaths is also used during static generation, specifically with dynamic routes.

In pages with dynamic routes, the content they display depends on the URL used to visit the page. For example, if a user visits /shows/123, they should see a page rendered using data from the TV show with ID 123. Since Next pre-renders these pages during build time in static generation, it should know all the IDs (paths) available on the website beforehand. We can retrieve this list using the getStaticPaths method.

Here’s how we will implement ID retrieval for the [tvshow].js page.

export async function getStaticPaths() {
    const res = await fetch("https://api.tvmaze.com/shows?page=1");
    const data = await res.json();

    //Get the available TV show IDs
    const paths = data.map(tvshow => {
        return { params: {tvshow: tvshow.id.toString()}}
    
    });

    //Pass the paths to prerender their content at build time
    return {paths, fallback: false} 
}

Here, we set fallback to false so that the app returns 404 errors for IDs that aren’t in the retrieved list.

When using the getStaticPaths method, we should also declare a getStaticProps method to enable Next to retrieve data on each TV show. We can implement this function as shown below.

export async function getStaticProps({params}) {

    //Params points to each TV show ID we retrieved in getStaticPaths

    //Retrieve data for the TV show with the given ID
    const res = await fetch(`https://api.tvmaze.com/shows/${params.tvshow}`);
    const data = await res.json();

    //Return the data as a prop to TVShow component
    return {props: {data}}
} 

We can then set up the TVShow component to show the retrieved data on the web page.

const TVShow = ({data}) => {
   
    return (
        <div>
            <h3>{data.name}</h3>
            <div>{data.summary.replace(/<\/?[^>]+(>|$)/g, "")}</div>
            <p>Language: {data.language}</p>
            <p>Status: {data.status}</p>
        </div>
    );
}

export async function getStaticPaths() {...}

export async function getStaticProps({params}) {...}

export default TVShow

This is how our TV show page displays these results.

Show information page

Show information page

getServerSideProps

If you use the getServerSideProps method to fetch data, Next retrieves data and generates HTML content for each client request. In other words, this method is used on pages that need server-side rendering. For example, if the data you want to retrieve constantly changes, you should use this function to get the latest results.

Here’s how we can retrieve data with this method when the client requests to view a specific TV show page.

export async function getServerSideProps(context) {

    //context contains the URL user requests
    const res = await fetch(`https://api.tvmaze.com/shows/${context.params.tvshow}`);
    const data = await res.json();
    return { props: { data } }
}

And then, the TVShow component can display the data of the requested TV show.

const TVShow = ({data}) => {
    return (
        <div>
            <h3>{data.name}</h3>
            <div>{data.summary.replace(/<\/?[^>]+(>|$)/g, "")}</div>
            <p>Language: {data.language}</p>
            <p>Status: {data.status}</p>
        </div>
    );
}

export async function getServerSideProps(context) {...}

export default TVShow

It returns an output similar to the previous implementation with getStaticPaths and getStaticProps. The only difference here is that the application doesn’t pre-render pages for each available TV show during the build. Instead, it retrieves data and renders the page only when the user sends a request to see a particular TV show.


Add CSS

Next provides several methods to add CSS to app components. Let’s discuss the ones that allow adding our own stylesheets in this section.

Global Stylesheets

You can add CSS properties that are common to all components on the website with this method. Before seeing how we can add this type of stylesheets, let’s create a new subdirectory named “styles” to store all CSS files in the app. Then, inside this directory, we can create a file named “globals.css” with the following global styles.

//globals.css 

html, body {
  padding: 0;
  margin: 0;
  font-family: Segoe UI, Roboto;
}

a {
  color: inherit;
}

Now, we should notify the Next app about the global stylesheet. For this, we use the _app.js file.

The _app.js file allows developers to override the default configurations Next defines for initializing an app. Even though this file is stored in the pages directory, it doesn’t convert to route in the web app.

We introduce the global stylesheet to Next by importing it inside _app.js.

//_app.js

import '../styles/globals.css'


//This code section should be in the _app.js by default
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

Now, when the app reloads, these global styles will apply to every web page.

Component-Level CSS

We can define CSS for different components in the app with the second method. A component-level CSS file, also stored inside the styles directory, should follow the name format [component-name].module.css. Then, we can apply these styles to the relevant component by simply importing the file and referring to the correct class names.

Here’s an example CSS file that should style the Home component in our app.

import Link from "next/link";
import styles from "../styles/Home.module.css";

const Home = ({data}) => (
  <div className={styles.container}>
    <h1>Hello World!</h1>
    <p>Popular TV Shows</p>
    <ul>
      {data.map(tvshow => {
        return (
          <li className={styles.item}>
            <Link href={`/shows/${tvshow.id}`}><a>{tvshow.name}</a></Link>
          </li>
        );
      })} 
    </ul>
    <Link href="/about"><a>Learn more about us</a></Link>
  </div>
);

Our home page after styling:

Better looking homepage, maybe&hellip;

Better looking homepage, maybe…

Styled-JSX

We can add CSS inside component-level JSX itself using styled-jsx blocks. In this method, we use a special tag, {`` }, and add the CSS code inside the quotes as we would in a regular CSS file.

Let’s modify our home page using a styled-jsx block.

import Link from “next/link”; import styles from “../styles/Home.module.css”;

const Home = ({data}) => (
  <div className={styles.container}>
    <h1>Hello World!</h1>
    <p>Popular TV Shows</p>
    <ul>
      {data.map(tvshow => {
        return (
          <li className={styles.item}>
            <Link href={`/shows/${tvshow.id}`}><a>{tvshow.name}</a></Link>
          </li>
        );
      })} 
    </ul>
    <Link href="/about"><a>Learn more about us</a></Link>

    <style jsx>{`
      a {
        text-decoration: none;
      }

      ul {
        list-style: none;
      }
    `}</style>
  </div>
);

Home page after modifications:

Definitely better looking home page

Definitely better looking home page


Wrapping Up

With that, we are concluding our introduction to Next.js tutorial. I hope this post convinced you to add Next.js to your arsenal of web development tools. Even if you have no previous experience working with React, Next makes it easy to find your footing with an intuitive set of features and fast performance. Considering that Next was first introduced to the world only five years ago, we will definitely be able to see it grow into an even better tool in the coming years.

Programming Web Development JavaScript React TypeScript