Introduction

Working on a modern JavaScript application powered by React is awesome until you realize a couple of problems related to rendering all the content on the client-side.

Problems:

  1. The page takes longer to become visible to the user because, before the content loads, all the JavaScript must load, and your application needs to run to determine what to show on the page.

  2. If you are building a publicly available website, you have a content SEO issue. Search engines are getting better at running and indexing JavaScript apps, but it's much better to send them pre-compiled content instead of letting them figure it out.

The solution to both of those problems is server rendering, also called static pre-rendering.

Next.js is a React framework to do all of this effortlessly and has many other features. 

The main features provided by Next.js

  • Hot Code Reloading - Next.js reloads the page when it detects any change saved to disk.

  • Automatic Routing - Any URL is mapped to the file-system, to files put in the pages folder, and you don't need any configuration.

  • Single File Components - Using styled-jsx, completely integrated as built by the same team, it's trivial to add styles scoped to the component.

  • Server Rendering - One of the main features which give you the possibility to render React components on the server-side before sending the HTML to the client.

  • Ecosystem Compatibility - Great compatibility with the Node and React ecosystem.

  • Automatic Code Splitting - Instead of generating one single JavaScript file containing all the app code, the app is broken up automatically by Next.js in several different resources, which means loading a page only loads the JavaScript necessary that particular page. This ensures your first-page load is as fast as it can be!

  • Prefetching - The Link component, which links together different pages, supports a prefetch prop, which automatically prefetches page resources in the background.

Installing Next.js

First of all, you need to have Node.js installed. You can get it on https://nodejs.org/.

There are 2 ways to install Next.js. The first way is by using the create-next-app command and the second one is the classic approach, which involves installing and setting up the Next.js app manually.

In this text, the first way will be explained.

In your command line, use the following line of code:

1npx create-next-app

The command asks the application name (and creates a new folder for you with that name), then downloads all the packages it needs (reactreact-domnext), sets the package.json to:

image-20200427-143719

and then, you can run the sample app by running npm run dev, which will show the result on  http://localhost:3000 like this:

image-20200427-144350

This is the recommended way to start a Next.js application, as it gives you structure and sample code to play with.

You can also use any examples stored on the Examples page using the --example option. For example, try:

1npx create-next-app --example blog-starter

Is SSR working?

To check if one of Next.js' main selling points of Next.js, which is server-side rendering, is working, while using Chrome, right-click anywhere on the page and select View Page Source.

Copy the code that you see on the screen and paste it into HTML Formatter for better observation. You will then see something like this:

image-20200427-151750

We have 4 JavaScript files being declared to be preloaded in the head, using rel="preload" as="script".

This tells the browser to start loading those files as soon as possible before the normal rendering flow starts. Without those, scripts would be loaded with an additional delay, improving the page loading performance.

Do you see the icon on the bottom right corner?

If you do, you can hover it and see that it says: “Prerendered Page“.

image-20200428-081431

This icon is only visible in development mode and tells you that the page does not depend on data that needs to be fetched at invocation time, and it can be prerendered and built as a static HTML file when we run npm run build.

Next.js can determine this by the absence of the getInitialProps() method attached to the page component.

When this is the case, our page can be even faster because it will be served statically as an HTML file rather than going through the Node.js server that generates the HTML output.

Another useful icon that might appear next to it, or instead of it on non-prerendered pages, is a little animated triangle:

image-20200504-090357

This is a compilation indicator and appears when you save a page, and Next.js is compiling the application before hot code reloading steps in to automatically reload the code in the application.

Adding a second page to the site

I want to add a Blog page to this website. It's going to be served into /blog, and for the time being, it will just contain a simple static page, just like our first index.js component:

image-20200428-095940

After saving the new file, the npm run dev process already running can render the page without restarting it.

When we go to URL http://localhost:3000/blog, our new page is shown:

image-20200428-095959 (1)

And it is also good to mention what the terminal told us:

image-20200428-100028

As we can see, the URL /blog depends on the file name and its position under the pages folder, and it doesn’t matter what the name of the component (our component’s name starts with capital “B“) is.

Linking the two pages

Now, since we have 2 pages, we can introduce links.

We won’t be using classic a tag because we are using Next.js, and we want fast transitions to other pages.  

If we use a tag to navigate our Blog page, we will get all the JavaScript from the server, but we don’t actually need it. We just need a blog.js page bundle, the only one that is new to the page.

To fix this problem, we use a component provided by Next, called Link.

First of all, we need to import it:

1import Link from 'next/link'

and then, we use it to wrap our link:

image-20200428-100123

If you compare loading times after clicking on the link with classic a tag and Link from Next.js in the developer tools in the Network tab, you will see how much faster the second method is. This is client-side rendering in action. Please don’t believe me, try it.  

Also, one more thing. What if you now press the back button? Nothing is being loaded because the browser still has the old index.js bundle in place, ready to load the /index route. It's all automatic.

Dynamic content with the router

In the previous chapter, we saw how to link the home to the blog page.

A blog is a great use case for Next.js, one we'll continue to explore in this chapter by adding blog posts.

This content is dynamic and might be taken from a database, markdown files, or more.

Next.js can serve dynamic content based on a dynamic URL.

We create a dynamic URL by creating a dynamic page with the [] syntax.

How? We add a pages/blog/[id].js file. This file will handle all the dynamic URLs under the /blog/ route.

In the file name, [id] inside the square brackets means that anything dynamic will be put inside the router's query property's id parameter.

 

But first of all, what is a router?

The router is a library provided by Next.js.

We import it from next/router:

1import { useRouter } from 'next/router'

and once we have useRouter, we instantiate the router object using:

1const router = useRouter()

Once we have this router object, we can extract information from it.

In particular, we can get the URL's dynamic part in the [id].js file by accessing the router.query.id.

The dynamic part can also just be a portion of the URL, like post-[id].js.

So let's go on and apply all those things in practice.

Create the file pages/blog/[id].js:

image-20200428-105859

Now, if you go to the http://localhost:3000/blog/new-post router, you should see this:

image-20200428-110359

We can use this id parameter to gather the post from a list of posts. From a database, for example. To keep things simple, we'll add a posts.json file in the project root folder:

1{
2  "first": {
3    "title": "first post",
4    "content": "Hey this is our first post!"
5  },
6  "second": {
7    "title": "second post",
8    "content": "Hey this is the second post content"
9  }
10}

Now we can import it and lookup the post from the id key:

image-20200428-114000

And reloading the page should give us the post with title and content. But it doesn’t… It gives us an error: Cannot read property 'title' of undefined.

During rendering, when the component is initialized, the data is not there yet. We'll see how to provide the data to the component with getInitialProps in the next chapter.

For now, we will use good old if (!post) return <p></p> before returning JSX:

image-20200428-114610

Change the URL's id to 'first', and now we have our first post visible on the page! Congratulations!

image-20200428-114830

After rendering, Next.js triggers an update with the query value, and the page displays the correct information, but if you view the source code, there is that empty <p> tag in the HTML.

We'll soon fix this issue that fails to implement SSR, and this harms both loading times for our users, SEO, and social sharing, as we already discussed.

We can complete the blog example by listing those posts in pages/blog.js:

image-20200428-122154

And we can link them to the individual post pages by importing Link from next/link and using it inside the posts loop:

image-20200428-123517

Prefetching

As mentioned, the Link Next.js component can be used to create links between 2 pages. When you use it, Next.js transparently handles frontend routing for us, so when a user clicks a link, the frontend takes care of showing the new page without triggering a new client/server request and response cycle, as it normally happens with web pages.

There's another thing that Next.js does for you when you use Link.

As soon as an element wrapped within <Link> appears in the viewport (which means it's visible to the website user), Next.js prefetches the URL it points to, as long as it's a local link (on your website), making the application super fast to the viewer.

This behavior is only being triggered in production mode (we'll talk about this in-depth later), which means you have to stop the application if you are running it with npm run dev, compile your production bundle with npm run build and run it with npm run start instead.

Using the Network inspector in the DevTools, you'll notice that any links above the fold, at page load, start the prefetching as soon as the load event has been fired on your page (triggered when the page is fully loaded and happens after the DOMContentLoaded event).

Any other Link tag not in the viewport will be prefetched when the user scrolls and gets into the viewport.

Prefetching is automatic on high-speed connections (Wifi and 3g+ connections unless the browser sends the Save-Data HTTP Header.

You can opt-out from prefetching individual Link instances by setting the prefetch prop to false:

1<Link href="/a-link" prefetch={false}>
2  <a>A link</a>
3</Link>

Using the router to detect the active link

One handy feature when working with links is determining what the current URL is, particularly assigning a class to the active link to style it differently from the other ones.

This is especially useful in your site header, for example.

The Next.js default Link component offered in next/link does not do this automatically for us.

We can create a Link component ourselves, and we store it in a file Link.js in the Components folder, and import that instead of the default next/link in our index.js.

In this component, we'll first import React from react, Link from next/link, and the useRouter hook from next/router.

Inside the component, we determine if the current pathname matches the component's href prop and if so, we append the selected class to the children.

We finally return children with the updated class, using React.cloneElement().

Here is the code for the new Link component :

1import React from 'react'
2import Link from 'next/link'
3import { useRouter } from 'next/router'
4
5export default ({ href, children }) => {
6  const router = useRouter()
7
8  let className = children.props.className || ''
9  if (router.pathname === href) {
10    className = `${className} selected`
11  }
12
13  return <Link href={href}>{React.cloneElement(children, { className })}</Link>
14}

Using next/router methods

We already saw how to use the Link component to handle routing in Next.js apps declaratively.

It's convenient to manage routing in JSX, but sometimes you need to trigger a routing change programmatically.

In this case, you can access the Next.js Router directly, provided in the next/router package, and call its push() method.

Here's an example of accessing the router:

1import { useRouter } from 'next/router'
2
3export default () => {
4  const router = useRouter()
5  //...
6}

Once we get the router object by invoking useRouter(), we can use its methods.

This is the client-side router, so methods should only be used in frontend facing code. The easiest way to ensure this is to wrap calls in the useEffect() React hook or inside componentDidMount() in React stateful components.

The ones you'll likely use the most are push() and prefetch().

push() allows us to trigger a URL change in the frontend programmatically:

1router.push('/login')

prefetch() allows us to programmatically prefetch a URL, useful when we don't have a Link tag that automatically handles prefetching for us:

1router.prefetch('/login')

Full example:

1import { useRouter } from 'next/router'
2
3export default () => {
4  const router = useRouter()
5
6  useEffect(() => {
7    router.prefetch('/login')
8  })
9}

You can also use the router to listen for route change events.

Feed data to the components using getInitialProps

Remember when we got the Cannot read property ‘title' of undefined error, and we had to use if (!post) to solve it? If you don’t, I’ve just reminded you that there is a more elegant way to solve it.

We will also make SSR work with dynamic routes.

We must provide the component with props, using a special function called getInitialProps() attached to the component.

To do so, first, we name the component:

1const Post = () => {
2  //...
3}
4
5export default Post

then we add the function to it:

1const Post = () => {
2  //...
3}
4
5Post.getInitialProps = () => {
6  //...
7}
8
9export default Post

This function gets an object as its argument, which contains several properties. In particular, we are interested in now that we get the query object, the one we used previously to get the post id.

So we can get it using the object destructuring syntax:

1Post.getInitialProps = ({ query }) => {
2  //...
3}

Now we can return the post from this function:

1Post.getInitialProps = ({ query }) => {
2  return {
3    post: posts[query.id]
4  }
5}

And we can also remove the import of useRouter, and we get the post from the props property passed to the Post component, so the final code should look like this:

image-20200428-145504

Now there will be no error, and SSR will be working as expected, which you can check by viewing the source.

The getInitialProps function will be executed on the server-side and the client-side when we navigate a new page using the Link component.

It's important to note that getInitialProps gets, in the context object it receives, in addition to the query object, these other properties:

  • pathname: the path section of URL

  • asPath - String of the actual path (including the query) shows in the browser

which in the case of calling http://localhost:3000/blog/first will respectively result to:

  • /blog/[id] /blog/test

CSS

For styling components in Next.js, we can use its library, which is styled-jsx built-in.

It affects only the component where the styled-jsx is applied.

The code is written like this:

image-20200428-164828

and in my opinion, it is good only for some smaller components where you need to add just a little bit of styling, but if the component has more elements, then it might be a little bit too much of a code mixed up in one component. In that case, my suggestion is to use a classic import method of a CSS file, which includes a simple configuration.

First, we need to install @zeit/next-css:

1npm install @zeit/next-css

and then create a configuration file in the root of the project, called next.config.js, with this content:

1const withCSS = require('@zeit/next-css')
2module.exports = withCSS()

After restarting the Next app, you can now import CSS like you normally do with JavaScript libraries or components:

1import '../style.css'

You can also import a SASS file directly, using the @zeit/next-sass library instead.

A styled-jsx library - explained in details.

Populating the head tag with custom tags

From any Next.js page component, you can add information to the page header.

This is handy when:

  • you want to customize the page title

  • you want to change a meta tag

How can you do so?

Inside every component, you can import the Head component from next/head and include it in your component JSX output:

image-20200428-170200

You can add any HTML tag you'd like to appear in the <head> section of the page.

When mounting the component, Next.js will make sure the tags inside Head are added to the page's heading. Same when unmounting the component, Next.js will take care of removing those tags.

API Routes

In addition to creating page routes, which means pages are served to the browser as Web pages, Next.js can create API routes.  It means that Next.js can be used to create a frontend for data stored and retrieved by Next.js itself, transferring JSON via fetch requests.

API routes live under the /pages/api/ folder and are mapped to the /api endpoint.

In those routes, we write Node.js code (rather than React code). It's a paradigm shift; you move from the frontend to the backend, but very seamlessly.

For example, you have a /pages/api/comments.js file, whose goal is to return the comments of a blog post as JSON.

And you have a list of comments stored in a comments.json file:

1[
2  {
3    "comment": "This is great!"
4  },
5  {
6    "comment": "Nice post!"
7  }
8]

Here's a sample code, which returns the client the list of comments:

1import comments from './comments.json'
2
3export default (req, res) => {
4  res.status(200).json(comments)
5}

It will listen on the /api/comments URL for GET requests, and you can try calling it using your browser:

image-20200428-212330

API routes can also use dynamic routing like pages, use the [] syntax to create a dynamic API route, like /pages/api/comments/[id].js, which will retrieve the comments specific to a post id.

Inside the [id].js, you can retrieve the id value by looking it up inside the req.query object:

1import comments from '../comments.json'
2
3export default (req, res) => {
4  res.status(200).json({ post: req.query.id, comments })
5}

And then you will get something like this:

image-20200429-070611

 

Run code only on the server-side or client-side

You can execute code only on the server-side or on the client-side by checking the window property in your page components.

This property is only existing inside the browser, so that you can check

1if (typeof window === 'undefined') {
2
3}

and add the server-side code in that block.

Similarly, you can execute client-side code only by checking.

1if (typeof window !== 'undefined') {
2
3}

Deploying the production version

Remember in the "How to install Next.js" chapter, and we added those 3 lines to the package.json script section:

1"scripts": {
2  "dev": "next",
3  "build": "next build",
4  "start": "next start"
5}

We used npm run dev up to now to call the next command installed locally in node_modules/next/dist/bin/next. This started the development server, which provided us source maps and hot code reloading, two handy features while debugging.

The same command can be invoked to build the website passing the build flag by running npm run build. The same command can then be used to start the production app, passing the start flag, by running npm run start.

Those 2 commands are the ones we must invoke to deploy the production version of our site locally.

So, let's create a production deployment of our app. Build it using:

1npm run build
image-20200428-220924

The command's output tells us that some routes (/ and /blog are now prerendered as static HTML, while /blog/[id] and /api/comments will be served by the Node.js backend.

Then you can run npm run start to start the production server locally:

1npm run start

Visiting http://localhost:3000 will show us the production version of the app locally.

Next steps

This was the basics tutorial about Next.js where I got to introduce you to the possibilities that Next.js can provide you with. There is a lot more to know about Next.js: managing user sessions with login, serverless, managing databases, and so on…

The next step I recommend is to take a good read at the Next.js official documentation to find out more about all the features and functionality I didn't talk about and take a look at all the additional functionalities introduced by Next.js plugins, some of which are pretty amazing.