Log In

Next.js introduction


by Yariv Katz

In this tutorial we will learn about the popular React framework Next.js. If you are not familiar yet with the term SSR - Server side rendering I strongly suggest to read the following article before diving to this one: React server side rendering. Our goal here is to cover the following topics with Next.js:

  • Routing
  • Static files
  • Styling

Hello World

First let's start with an app that displays hello world. Create a new directory for your project init npm in that directory and install react, react-dom and next.

> mkdir next-tutorial
> cd next-tutorial
> npm init --yes
> npm install --save next react react-dom

After installing next you will have the next command exposed in the terminal which you can use to build your project, start a development server, start next node server. Let's modify the package.json and add a scripts section with the following code:

{
  "name": "next-tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}

Create the directory pages. The pages directory has a special meaning and every js file you will put there will have a route leading to that page. In the pages directory create the file index.js This will be your homepage.

> mkdir pages
> touch pages/index.js

Let's create our Hello World application. In the index.js place the following code:

import React from 'react';

export default () => {
    return (
        <h1>Hello world from Next.js</h1>
    );
}

We are simply displaying an hello world message, now let's understand how to activate our application.

Running our App

We have few two ways to run our application, production and development.

Development

In your terminal type:

> npm run dev

This will start a development server with hot code reloading. You will also have source maps to your files to easily add breakpoint in chrome developer tools. If you open your browser to localhost:3000 you will notice there is a full html of your site so the dev server is also server side rendered.

Production

The following commands will run your app in production mode.

> npm run build
> npm start

This will run your app in production where the js is minified, no source maps and ready for production.

Routing

When you add a page to the pages directory, next will place a route to that page according to the filename. Let's create an about page. In the pages directory, create the file about.js with the following:

import React from 'react';

export default () => {
    return (
        <h1>About us</h1>
    );
}

Activate the dev server and try to go to the about page by placing the url: http://localhost:3000/about

Layout - Navigation bar

Our next job is to create a navigation bar to navigate between the homepage and the about page. The navigation bar will be common to all the pages, so it might be better to create a common layout page for all the other pages. Create the folder: components and in that folder create a file called: Layout.js with the following code:

import React from 'react';
import Link from 'next/link';

export default ({children}) => {
    return (
        <div>
            <nav>
                <ul>
                    <li>
                        <Link href="/">
                            <a>Home</a>
                        </Link>
                    </li>
                    <li>
                        <Link href="/about">
                            <a>About</a>
                        </Link>
                    </li>
                </ul>
            </nav>
            {
                children
            }
        </div>
    )
}

We created a layout component containing a navigation bar. With Next for the routing we will used the buildin router, and to link between the pages using the router we will use the Link component. The Link component will get href property to where to link and also a clickable child to place the link on. We need to modify the index.js and about.js to use our layout. index.js

import React from 'react';
import Layout from '../components/Layout';

export default () => {
    return (
        <Layout>
            <h1>Hello world from Next</h1>
        </Layout>
    );
}

about.js

import React from 'react';
import Layout from '../components/Layout';

export default () => {
    return (
        <Layout>
            <h1>About us</h1>
        </Layout>
    );
}

The components folder we created is not reserved, there are a few reserved directories but the others you can use whatever name you would like. Also note the the routing is based on JS after you app loads so you still have the benefits of fast SPA routing.

Static files

In a Next.js application, we place our static files in a special folder called static. Create that directory and place an image there called logo.jpg In the Layout.js let's place our image, so modify the Layout.js code to this:

import React from 'react';
import Link from 'next/link';

export default ({children}) => {
    return (
        <div>
            <nav>
                <ul>
                    <li>
                        <Link href="/">
                            <a>Home</a>
                        </Link>
                    </li>
                    <li>
                        <Link href="/about">
                            <a>About</a>
                        </Link>
                    </li>
                </ul>
            </nav>
            {
                children
            }
            <div>
                <img src="/static/logo.jpg" />
            </div>
        </div>
    )
}

Notice at the bottom of the component we are placing our image by simply pointing the src attribute to the static folder and to the name of our file.

Styling our app

We will cover the following popular styling techniques:

styled-components

With styled-components we create components with styles in them. In this way we can express our global app styles as simple components so h1 tag will turn to H1 component with styles. And our components will be described by our global styles components and also have styles of their own. We will start by installing styled-components

> npm install --save styled-components

create a directory called: styles and in it create another directory: components. This directory will contain our styled components that describe our app styling language. In it we will sort our component in folders based on the styles category. For example we might have a folder called typography with components like H1, H2, etc. Create in the styles/components the folder typography and in it create the file H1.js with the following:

import styled from 'styled-components';

const H1 = styled.h1`
    text-align: center;
    text-decoration: underline;
    font-size: ${({isBig}) => isBig ? '30px' : '16px'}
`

export default H1;

We made the font-size dynamic so we can get a property in our <H1 isBig={false} /> Other than that we just place regular css code and use the styled function.

Now in our index.js, about.js we can design our components using our styled components and also attach styles to the components of their own. Modify the index.js

import React from 'react';
import Layout from '../components/Layout';
import H1 from '../styles/components/typography/H1';
import styled from 'styled-components';

const Index = ({className}) => {
    return (
        <Layout >
            <H1 className={className}>Hello world from Next</H1>
        </Layout>
    );
}

export default styled(Index)`
        color: red;
`

Notice that we replaced the h1 tag with our styled H1 and also add styles to the component by using the styled function. Now modify the file about.js

import React from 'react';
import Layout from '../components/Layout';
import H1 from '../styles/components/typography/H1';
import styled from 'styled-components';

const About = ({className}) => {
    return (
        <Layout>
            <H1 className={className} isBig>About us</H1>
        </Layout>
    );
}

export default styled(About)`
    color: green;
`;

We did a similar thing with the about component. styled-components is placing the styles in the head tag, and since Next is in charge of creating the head we need to figure out how we can customize it. Next allows you to modify all those top of the html tags (html, head, etc.) by extending Next Document and placing it in ./pages/_document.js. The Document class will contain styles properties you can override, and in styled-components docs there is instructions of code that needs to be placed in order to access styled-components styles and override the original styles. Create the file: ./pages/_document.js with the following:

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps (ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      }
    } finally {
      sheet.seal()
    }
  }
}

We are overriding the props and extending the styles of the props. The Document is only run on the server side.

Another thing we will have to do is add the babel plugin for styled components to add server side rendering support. The plugin is called: babel-plugin-styled-components install it with npm:

> npm install babel-plugin-styled-components -D

Now we need to tell babel to use this plugin so in the root folder, create the file .babelrc with the following:

{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true,
        "displayName": true,
        "preprocess": false
      }
    ]
  ]
}

We are setting the plugin to add SSR support so we are now ready to go, relaunch next dev server and you should see different styles in our components, those styles are created in the server side and then the client takes the wheel and continue to add the styles.

Styling with SCSS

Different approach to styling is to use external style files, and use CSS or other technology that compiles to CSS like SCSS, SASS, Less, etc. We are going to demonstrate styling our Next.js with SCSS. So we are aiming for our styles to be compiled to css and included in the static folder and served with our js files. We are also aiming for managing the styles the same way we did with the styled-components and separate the styles to global styles and to component styles. The global styles will be managed as a SCSS project, and the component styles will be defined as a file next to the component file and will be included as CSS modules so we can encapsulate the styles to the component.

To make Next.js compile our SCSS files we will have to install node-sass and also Next.js plugin for compiling the SCSS and placing the CSS file in the static directory. Install the following packages using NPM

> npm install --save @zeit/next-sass node-sass @zeit/next-css

We will also need to configure Next.js to use the @zeit/next-sass plugin. In the root of the place the file next.config.js this file is a node module containing advanced custom configuration for Next.js and in here we can tell Next.js to use the @zeit/next-sass plugin. Place the following code in that file:

const withSass = require('@zeit/next-sass');
const withCSS = require('@zeit/next-css');
const path = require('path');

module.exports = withCSS(withSass({
  cssModules: true,
  cssLoaderOptions: {
    // localIdentName: "[name]__[local]",
    getLocalIdent: (loaderContext, localIdentName, localName, options) => {
      const fileName = path.basename(loaderContext.resourcePath)
      if(fileName.indexOf('global.scss') !== -1){
        return localName
      }else{
        const name = fileName.replace(/\.[^/.]+$/, "")
        return `${name}__${localName}`
      }
    }
  },
}));

withCSS, withSass allows us to configure the css and scss loaders. We want to use css modules cause they will be helpfull for our components private styles, we also want to use regular classes for our global styles. This means we will have to override the css loader to not change the css class names (like he would do for css modules) when you stumble on an import containing global.scss

We are now ready to start the global styling of our app. In the styles directory create our SCSS entry point, a file called: main.global.scss Remember our global styles will contain styling language for our entire app, the styles that are only repeated in a certain react component, will be placed next to that component. In the styles/main.scss place the following code.

@import "./partials/typography";

Our entry point file will simply @import our style files where we will divide our styles according to the styles section of our app like: _typography.scss, _forms.scss, _buttons.scss, etc. Let's create our typography file, in the styles folder create another folder called: partials which will contain the styles sections files, and create a new file called _typography.scss with the following:

h1 {
    text-align: center;
    text-decoration: underline;
    font-size: 16px;
    &.big {
        font-size: 30px;
    }
}

We did here something similar to what we did with the styled-components library we placing design for h1 where we can add a class big to make it a bit bigger. Now we want to include our global styles to all of our page, so the best place to import it is in the Layout component. Edit the components/Layout.js and add the following at the top:

import React from 'react';
import Link from 'next/link';
import '../styles/main.scss';

...

Notice that we add an import to our SCSS entry point which means our global style project will be included with all the pages since the layout is used in all the pages. To use the styles in our pages, modify the pages/index.js to the following:

import React from 'react';
import Layout from '../components/Layout';

export default () => {
    return (
        <Layout >
            <h1 className="big">Hello world from Next</h1>
        </Layout>
    );
}

We are using the regular h1 tag and placing the big class name. let's run our app using:

> npm run dev

Go to the root url and you should see our h1 now has styles.

Time to deal with the private styles, What I like to do is place each component that has private styles that relate only to that component, I will place the component in a directory along with the styles file and the test file for that component in the same directory. You can also place the components in the pages directory in a directory of their own, just as long as you make sure to rename you file to index.js Another way you can arrange the pages folder in sub folders is by using another Next.js plugin called next-routes. We will install it using NPM

> npm install next-routes --save

The package we installed allow us to define universal routes for the express side and the frontend react side. In your root folder create the file routes.js and in it place the following:

const routes = require('next-routes')

module.exports = routes()
    .add('index', '/', 'index')
    .add('about/about', '/about')

We are simply telling in the first argument where to find the file of the page, and on the second argument the path to map that file to. This means that in the pages directory we need to create two directories: pages/about/ and pages/index Create those directories and move the about.js file to the about directory and the index.js to the index directory. We also have to make our own server.js file so our server will work with the new router. In the root of the project, create the file server.js with the following code:

const next = require('next')
const routes = require('./routes')
const app = next({dev: process.env.NODE_ENV !== 'production'})
const handler = routes.getRequestHandler(app)

// With express
const express = require('express')
app.prepare().then(() => {
    express().use(handler).listen(3000)
})

We are using an express server here which means we will have to install express

> npm install express --save

In the server.js file we are loading an express app with the request handler created from the routes file we created. Then we load that to the express app which creates our express server with the proper routing. We will also need to modify our navigation bar, the Link component should be taken from the routes.js file we created. Modify the Layout.js file:

import React from 'react';
import {Link} from '../routes';
import '../styles/main.global.scss';

export default ({children}) => {
    return (
        <div>
            <nav>
                <ul>
                    <li>
                        <Link route="/">
                            <a>Home</a>
                        </Link>
                    </li>
                    <li>
                        <Link route="/about">
                            <a>About</a>
                        </Link>
                    </li>
                </ul>
            </nav>
            {
                children
            }
        </div>
    )
}

We are simply using the Link from the route.js file we created. Now modify the package.json to activate our server:

...
"scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "next start"
  },
...

now when you run npm run dev it will run our express server. Now we can place our private styles arranged in a folder next to our js file. In the pages/about folder, create the file about.scss with the private styles for the about page and the following code:

h1.big {
    font-size: 60px !important;
}

For the specific page I want to override the big class and make the font a bit bigger. Remember that this will be a css module, so we should not worry about collision with the big class since the big class here the name will change. To use the css module modify the about.js file to the following:

import React from 'react';
import Layout from '../../components/Layout';
import aboutStyles from './about.scss';

export default () => {
    return (
        <Layout>
            <h1 className={aboutStyles.big}>About us</h1>
        </Layout>
    );
}

Notice that in order to use our big class we need to import the css module and from the mapping object grab the big key which will map to the real class name. In this way we can use SCSS to design our app.

Faster initial load with server query

One of the benefits of a web app with SSR is the fast initial load. Our server understands our React code and can translate the react code to an HTML and give the browser a quick HTML to process before the JS code kicks in and our app becomes an SPA. Maybe we can make our code even faster! Let's demonstrate a common use case, we have a page which needs to load a list from a REST server. Our REST server is located at the url: https://academeez-chat.herokuapp.com/api/messages/ this will return a list of messages from the server and we want to display a page that displays those list of messages. Now we can make this messages pages a hell of a lot faster if we query the rest server from our server side, populate the html and then in the client side we won't have to query the server and use the same list the server got for us. This will speed things up cause we our saving the client side request to fetch the messages, and our server will make the request much faster since usually our REST server will be in the same VPN as our next.js server. Also in case we are not fetching the messages page from the server and we started from another page and transferred from the client to the messages page via a link using the history js object we do want a request to be send from our client. In order to achieve this let's create another page. In the pages directory, create another directory called messages and inside it place the file messages.js With the following code:

import React from 'react';
import axios from 'axios';
import Layout from '../../components/Layout';

const Messages = ({messages}) => {
    return (
        <Layout>
            <ul>
                {
                    messages.map((singleMessage) => (<li key={singleMessage.id}>{singleMessage.message}</li>))
                }
            </ul>
        </Layout>

    )
}

Messages.getInitialProps = async () => {
    const response = await axios.get('https://academeez-chat.herokuapp.com/api/messages/');
    const data = response.data;
    return {messages: data};
}

export default Messages;

Notice that our component is implementing the async method getInitialProps if your component is a class this would be an async method inside your class. This method can query the server, in our case we are using axios to query the server which you will need to install using npm: npm install axios the object which we are returning from this method will be added to the component properties. Now modify the routes.js file and add the route to our new page:

const routes = require('next-routes')

module.exports = routes()
    .add('index', '/', 'index')
    .add('about/about', '/about')
    .add('messages/messages', '/messages')

And let's add a navigation to this page, so modify the Layout.js file:

import React from 'react';
import {Link} from '../routes';
import '../styles/main.global.scss';

export default ({children}) => {
    return (
        <div>
            <nav>
                <ul>
                    <li>
                        <Link route="/">
                            <a>Home</a>
                        </Link>
                    </li>
                    <li>
                        <Link route="/about">
                            <a>About</a>
                        </Link>
                    </li>
                    <li>
                        <Link route="/messages">
                            <a>Messages</a>
                        </Link>
                    </li>
                </ul>
            </nav>
            {
                children
            }
            <div>
                <img src="/static/logo.jpg" />
            </div>
        </div>
    )
}

Now let's test our app. Notice that if you open your browser on the messages page you won't see the ajax request sent from the client, yet the list will be populated, this means that the server populated the list and did it super fast. But if you go to the messages page by using the navigation bar the client will send an ajax request. This feature will also add to our app being super fast with Next.js

Summary

I really hope, that after this article you will understand what all the fuss about regarding Next.js. Google published that if our app is loading on a slow connection on a mobile device more than 3 seconds we will lose more than 50% of our users. This means our app needs to load super fast. If you choose React, and if you have to have fast initial load with SSR there is no better choice than Next.js