Monday, 3 June, 2019 UTC


Summary

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
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
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.
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.
npm install isomorphic-unfetch
A popular approach to 
getInitialProps
 is to use async/await. Let’s combine it with 
isomorphic-unfetch
.
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)
  • asPath – the URL as a whole
  • pathname – 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
  • res – HTTP response object\
We can, for example, use it to limit the number of posts that we want to render.
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.
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.
.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.
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
const withSass = require('@zeit/next-sass');
module.exports = withSass();
/components/Posts/index.js
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
.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
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.
The post React SSR with Next.js #2. Prefetching the data with getInitialProps appeared first on Marcin Wanago Blog - JavaScript, both frontend and backend.