- 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)
- 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.
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.
Thanks for the good information. I am just thinking about using next.js. Two questions:
• Can one still use componentDidMount to fetch data on the client side or is that now bad practice?
• Can one use next.js to generate static pages, but still have a dynamic page? I’m assuming it must be possible with configuration on the server side — where the ‘static’ pages are delivered as is.
thanks