June 3, 2019











1. React SSR with Next.js #1. Concept of Server Side Rendering & basics of routing

2. React SSR with Next.js #2. Prefetching the data with getInitialProps

Today we expand our knowledge to build pages that are more complex and utilize more features from the Next.js framework. In this article, we learn how to fetch initial data for our components on the server side. This way, we can take even more work off the client side.

Fetching data

First, let’s display some data that we fetch from a REST API.

/pages/index.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React , { Component } from 'react' ; import Posts from '../components/Posts' ; class Home extends Component { state = { posts : [ ] } ; componentDidMount ( ) { fetch ( 'https://jsonplaceholder.typicode.com/posts' ) . then ( postsResponse = > postsResponse . json ( ) ) . then ( ( posts ) = > { this . setState ( { posts } ) } ) } render ( ) { const { posts } = this . state ; return ( < div > < Posts posts = { posts } / > < / div > ) } } export default Home

/components/Posts/index.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import React from 'react' ; const Posts = ( { posts } ) = > ( < div > { posts . map ( post = > ( < div key = { post . id } > < h2 > { post . title } < / h2 > < p > { post . body } < / p > < / div > ) ) } < / div > ) ; export default Posts ;

In the above code, we fetch a list of posts and then display them. Let’s see how it turns out!

As you can see in the DevTools, the posts don’t render on the server. The above happens because the componentDidMount function runs only at the browser. For similar functionality, we need to use the getInitialProps.

getInitialProps

The getInitialProps function runs both on the server and in the client. It is static and should return a promise. Anything that promise resolves becomes initial props of our component.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import React , { Component } from 'react' ; import Posts from '../components/Posts' ; class Home extends Component { static getInitialProps ( ) { return fetch ( 'https://jsonplaceholder.typicode.com/posts' ) . then ( postsResponse = > postsResponse . json ( ) ) . then ( ( posts ) = > { return { posts } } ) ; } render ( ) { const { posts } = this . props ; return ( < div > < Posts posts = { posts } / > < / div > ) } } export default Home

If you would like to know more about promises, check out Explaining promises and callbacks while implementing a sorting algorithm

There is just one small issue with the code above. As we’ve noted in the previous part of this series, our code runs in two different environments. We need to remember that the Fetch API does not exist in the Node.js environment.

There are a few ways in which we can approach it. If we still want to use Fetch API in our client-side code, we can use a package called isomorphic-unfetch. When you are in the browser, it uses the unfetch polyfill. If your code runs in Node.js, it switches to node-fetch.

1 npm install isomorphic - unfetch

A popular approach to getInitialProps is to use async/await. Let’s combine it with isomorphic-unfetch.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React , { Component } from 'react' ; import Posts from '../components/Posts' ; import fetch from 'isomorphic-unfetch' ; class Home extends Component { static async getInitialProps ( ) { const postsResponse = await fetch ( 'https://jsonplaceholder.typicode.com/posts' ) ; const posts = await postsResponse . json ( ) ; return { posts } } render ( ) { const { posts } = this . props ; return ( < div > < Posts posts = { posts } / > < / div > ) } } export default Home

If you want to know more about async/await, take a look at Explaining async/await. Creating dummy promises

Let’s check out the server response now.

As presented in the DevTools, this time the server responds with fully rendered posts. The above happens because the Next.js framework waits for the getInitialProps to resolve before rendering and sending the response.

The context within getInitialProps

The getInitialProps receives a context object with properties:

query – query section of URL (parsed as an object)

– query section of URL (parsed as an object) asPath – the URL as a whole

– the URL as a whole pathname – the path section of URL

– the path section of URL err – error object, if any occurs

If the getInitialProps function runs on the server, it also contains:

req – HTTP request object

– HTTP request object res – HTTP response object\

We can, for example, use it to limit the number of posts that we want to render.

1 2 3 4 5 6 7 static async getInitialProps ( { query } ) { const postsResponse = await fetch ( 'https://jsonplaceholder.typicode.com/posts' ) ; const posts = await postsResponse . json ( ) ; return { posts : posts . slice ( 0 , query . numberOfPosts ) } }

In the example above, if we dont provide query.numberOfPosts, the posts.slice(0, undefined) returns the original array

CSS support

So far we’ve only dealt with HTML and JS without any styling. With the Next.js framework, we can include CSS in our projects in a few different ways. The first one would be using the styled-jsx library because it comes with the Next.js framework.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import React from 'react' ; const Posts = ( { posts } ) = > ( < div > <style jsx> { ` .post { max-width : 500px ; margin : 0 auto 20px auto ; } .post h2 { margin-bottom : 10px ; } ` } </style> { posts . map ( post = > ( < div className = "post" key = { post . id } > < h2 > { post . title } < / h2 > < p > { post . body } < / p > < / div > ) ) } < / div > ) ; export default Posts ;

It isn’t very powerful though and doesn’t support features like nesting.

1 2 3 4 5 6 7 .post { max-width : 500px ; margin : 0 auto 20px auto ; h2 { margin-bottom : 10px ; } }

Using sass or css

Another approach that we could take would be to import .css or .scss files. To do that, we need additional plugins. We use .scss files in this example.

1 npm install @ zeit / next - sass node - sass

To incorporate it into our project, we need to create a next.config.js file.

next.config.js

1 2 const withSass = require ( '@zeit/next-sass' ) ; module . exports = withSass ( ) ;

/components/Posts/index.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React from 'react' ; import './style.scss' ; const Posts = ( { posts } ) = > ( < div > { posts . map ( post = > ( < div className = "post" key = { post . id } > < h2 > { post . title } < / h2 > < p > { post . body } < / p > < / div > ) ) } < / div > ) ; export default Posts ;

/components/Posts/style.scss

1 2 3 4 5 6 7 .post { max-width : 500px ; margin : 0 auto 20px auto ; h2 { margin-bottom : 10px ; } }

The Next.js framework automatically adds our files to the output HTML. In the production build, all scss files have a hash in the filename. Thanks to that we prevent the browser from caching if there is a new version of the stylesheets.

We can also use CSS modules if we prefer to, but we need to pass additional configuration to the withSass function.

next.config.js

1 2 3 4 const withSass = require ( '@zeit/next-sass' ) ; module . exports = withSass ( { cssModules : true } ) ;

Summary

In this article, we’ve covered how to prefetch the data using getInitialProps. The examples that we’ve used show that doing it the right way can improve the performance of our application because the data is fetched on the server. That means that the browser does not have to do it and rerender the view.