@MaximeHeckel

Data Fetching with NextJS: What I learned

May 5, 2020 / 9 min read

Last Updated: May 5, 2020

As I wrote in my blog post titled Learn In Public, I recently brought my attention to learning more about NextJS. Throughout my entire career as a Frontend engineer, I've solely focused on building client-side React applications without much looking at other solutions. NextJS, in the meantime, became very popular and I kept hearing good things about it and when seeing the different use cases of server-side rendering. I can now see myself using it in several projects, or for my potential future business endeavors.

If like me, you started learning NextJS, or need a refresher on the latest features, this article is for you!

Wrapping my head around data fetching

One aspect that struck me the most when I started to learn the framework, was data fetching. NextJS provides different functions to get data asynchronously, and when to use each function can seem tricky at first, as it all depends on different use cases:

  • ArrowAn icon representing an arrow
    the pre-rendering type desired: server-side rendered or static
  • ArrowAn icon representing an arrow
    the data: are we loading static or dynamic data? Is that data accessible ahead of a user request

Additionally, you can see this in the title of this post, the names of these functions look quite similar which can be confusing at first, but bear with me, at the end of this article it will be clearer.

Pre-Rendering types with NextJS

As mentioned above, NextJS provides support for server-side rendering (SSR) and static-site generation (SSG) which are both pre-rendering types, and aim for the same result (i.e. better performance, no client-side rendering) but differ in when the HTML pages are being pre-rendered:

  • ArrowAn icon representing an arrow
    For SSG, the HTML pages are rendered at build time. When you're done building, the result is a set of static files. This is the method I prefer because it allows me to cache my website on a CDN and offers better performance
  • ArrowAn icon representing an arrow
    For SSR, the site is generated by a server on each request. That means your website is dependent on some code running on a server, not simply static files.

Now let's look at the difference between these two pre-rendering methods when it comes to data fetching

getStaticProps

This function is the first one I encountered when I started to learn NextJS. It's an asynchronous function that fetches the data at build time, which means according to the definitions we just saw above that it's used for SSG.

It takes a context as an argument (more on that later) and will return an object with a props field. This prop field contains all the props we'll end up passing down to a page.

This function can be used to :

  1. ArrowAn icon representing an arrow
    call an API / external endpoint and retrieve some data
  2. ArrowAn icon representing an arrow
    read a local file
  3. ArrowAn icon representing an arrow
    query a database

as long as the data to be retrieved is not user-related or a specific entity linked to the client as, again, this function will be triggered at build time. It also means that each time the data changes, we would have to rebuild.

Example showcasing a use case of getStaticProps for a sample /todos API

1
import { NextPage, GetStaticProps } from 'next';
2
import Link from 'next/link';
3
import fetch from 'node-fetch';
4
5
const TodosPage: NextPage<{
6
todos: { title: string; userId: string; id: string; completed: boolean }[];
7
}> = (props) => {
8
return (
9
<>
10
<h1>Todos page</h1>
11
<Link href="/">Home</Link>
12
<ul>
13
{props.todos.map((x) => {
14
return (
15
<li key={`/todos/${x.id}`}>
16
<Link as={`/todos/${x.id}`} href={`/todos/[id]`}>
17
{x.title}
18
</Link>
19
</li>
20
);
21
})}
22
</ul>
23
</>
24
);
25
};
26
27
export const getStaticProps: GetStaticProps = async () => {
28
const todos = await fetch(
29
'https://jsonplaceholder.typicode.com/todos'
30
).then((response) => response.json());
31
32
return {
33
props: { todos },
34
};
35
};
36
37
export default TodosPage;

getStaticPaths

NextJS gives the ability for the developer to create statically generated dynamic routes, e.g. /products/:id or /users/:name/:comment. To have access to the path of these dynamic routes we use getStaticPaths. Same as the function we introduced earlier, this one is an asynchronous function that returns an object with a paths field. That paths field contains all the paths that need to pre-rendered at build time, which means it's used for SSG. There's also another field called fallback, I haven't used it yet, the only thing I know is that if it's set to false and a user tries to access a path that is not returned by getStaticPaths, it will result in a 404.

getStaticPaths can be used in conjunction with getStaticProps. The paths returned are then present in the "context" of the page, which can be read by the getStaticProps function. As an example, we could consider a static product page with a dynamic route representing a route to a given product:

Example showcasing a use case of getStaticPaths for a sample /todos/:id API

1
import { NextPage, GetStaticPaths, GetStaticProps } from 'next';
2
import Link from 'next/link';
3
import fetch from 'node-fetch';
4
5
const TodoPage: NextPage<{ title: string }> = (props) => {
6
return (
7
<>
8
<p>{props.title}</p>
9
<Link href="/todos">Todos</Link>
10
</>
11
);
12
};
13
14
export const getStaticPaths: GetStaticPaths = async () => {
15
const todos = await fetch(
16
'https://jsonplaceholder.typicode.com/todos'
17
).then((response) => response.json());
18
19
const ids = todos.map((todo) => todo.id);
20
const paths = ids.map((id) => ({ params: { id: id.toString() } }));
21
22
return {
23
paths,
24
fallback: false,
25
};
26
};
27
28
export const getStaticProps: GetStaticProps = async ({ params: { id } }) => {
29
const todos = await fetch(
30
'https://jsonplaceholder.typicode.com/todos'
31
).then((response) => response.json());
32
const todo = todos.find((x) => x.id == id);
33
34
return {
35
props: {
36
title: todo.title,
37
},
38
};
39
};
40
41
export default TodoPage;

getServerSideProps

Sometimes static-side generation is not what we need. If we want to be able to fetch data and render dynamic content on the fly getServerSideProps is what we need. Like getStaticProps, this function is asynchronous and allows us to fetch some data and returns an object with a props field that will be passed down to a page. However, the main difference here is that getServerSideProps allows us to pre-render a page on each request, thus we can consider that this is a use case for SSR. This means that thanks to this function, I can go fetch some non-static data that is tied to a request.

Example showcasing a use case of getServerSideProps for a sample /todos/:id API

1
import { GetServerSideProps, NextPage } from 'next';
2
import ErrorPage from 'next/error';
3
import fetch from 'node-fetch';
4
5
interface Data {
6
id: number;
7
title: string;
8
userId: number;
9
completed: boolean;
10
}
11
12
const Todo: NextPage<{ data: Data }> = (props) => {
13
if (!props.data) {
14
return <ErrorPage statusCode={404} />;
15
}
16
17
return (
18
<>
19
<p>{props.data.id}</p>
20
<p>{props.data.title}</p>
21
<p>{props.data.userId}</p>
22
<p>{props.data.completed}</p>
23
</>
24
);
25
};
26
27
export const getServerSideProps: GetServerSideProps = async ({
28
params,
29
res,
30
}) => {
31
try {
32
const { id } = params;
33
const result = await fetch(
34
`https://jsonplaceholder.typicode.com/todos/${id}`
35
).then((response) => response.json());
36
37
return {
38
props: {
39
data: result,
40
},
41
};
42
} catch {
43
res.statusCode = 404;
44
return {
45
props: {},
46
};
47
}
48
};
49
50
export default Todo;

What about data fetching on "non-page" components

I felt that one of the most confusing aspect of NextJS when having worked only on more classic client side rendered app, is the distinction between pages and "non-pages" components. Thankfully, I'm a heavy gatsbyJS user so I was already familiar with these concepts when I started learning about NextJS, hence, just in case I want to write a few word about it for anyone who might still be confused about pages vs components.

The functions featured above only work on pages. Which means the following:

  • ArrowAn icon representing an arrow
    you can only use them in files under the /pages folder
  • ArrowAn icon representing an arrow
    they can't be used on components
  • ArrowAn icon representing an arrow
    components will have to rely on client-side fetching if we want them to fetch data.

So when building your first NextJS app, you'll have to carefully architect your app and think about data beforehand and ask yourself:

  • ArrowAn icon representing an arrow
    what can and should be fetched at build time?
  • ArrowAn icon representing an arrow
    what can and should be server-rendered?
  • ArrowAn icon representing an arrow
    which components need to have access to data and do they need to trigger some client-side fetching.

These ways of thinking about where the data comes from in these different use cases are unusual to me since I only worked on client-side apps in the past, but I feel ready to do that kind of gymnastic in my future projects as I feel these NextJS functionalities have a lot of potentials when it comes to building more efficient applications.

Liked this article? Share it with a friend on Bluesky or Twitter or support me to take on more ambitious projects to write about. Have a question, feedback or simply wish to contact me privately? Shoot me a DM and I'll do my best to get back to you.

Have a wonderful day.

– Maxime

GetServerSideProps vs GetStaticProps vs GetStaticPaths.