Data Fetching with NextJS: What I learned

May 5 2020

/ 5 min read /

0 Likes

0 Replies

0 Reposts

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:

  • the pre-rendering type desired: server-side rendered or static
  • 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:

  • 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
  • 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. call an API / external endpoint and retrieve some data
  2. read a local file
  3. 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

tsx
1import { NextPage, GetStaticProps } from "next";
2import Link from "next/link";
3import fetch from "node-fetch";
4
5const 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
27export 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
37export default TodosPage;

Note: To use fetch with NextJS SSR and SSG functions, we need to use node.fetch as these API calls will be done during build time or request time, hence on the server side which means we can't use window.fetch.

When would I use this?

If I were to use NextJS on a project, I'd most probably use this function of the framework to:

  • get a list of articles I'm selling on my own store
  • generate markdown based documentation or articles
  • get some public data from a cloud function (i.e. like, repost counts on a blog post)

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

tsx
1import { NextPage, GetStaticPaths, GetStaticProps } from "next";
2import Link from "next/link";
3import fetch from "node-fetch";
4
5const TodoPage: NextPage<{ title: string }> = (props) => {
6 return (
7 <>
8 <p>{props.title}</p>
9 <Link href="/todos">Todos</Link>
10 </>
11 );
12};
13
14export 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
28export 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
41export default TodoPage;

When would I use this?

From what I understand, I'd use it in the same use case as the getStaticProps except I would do it to display a detail page of an entity like a product, documentation entry, or blog post. It would also be required for any client-side fetching that requires one of the parameters of the path.

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

tsx
1import { GetServerSideProps, NextPage } from "next";
2import ErrorPage from "next/error";
3import fetch from "node-fetch";
4
5interface Data {
6 id: number;
7 title: string;
8 userId: number;
9 completed: boolean;
10}
11
12const 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
27export 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
50export default Todo;

When would I use this?

If I were to use NextJS on a project, I'd most likely use this to:

  • fetch some more complex data that requires computation.
  • fetch some time-sensitive data that changes over time and can't just be fetched at build time.
  • fetch some specific user-related data like permissions, that can be changed or revoked.

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:

  • you can only use them in files under the /pages folder
  • they can't be used on components
  • 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:

  • what can and should be fetched at build time?
  • what can and should be server-rendered?
  • 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.

Fetching Replies...

Do you have any questions, comments or simply wish to contact me privately? Don’t hesitate to shoot me a DM on Twitter.


Have a wonderful day.
Maxime


© 2020 Maxime Heckel —— Made in SF. Polished in NY.