Ryan Cao

Data Fetching with Next.js

As everybody knows, most web apps must fetch user data in some way and serve it to the client. There are many many ways to do this, and in this article we're going to cover a few.

Client-side

Serving the data client-side gives your users a fast experience, because then they can see a speedy fallback page while you load your data rather waiting for it to load server-side. This is best for dashboard apps, not for content-based websites.

(Personal opinion, comment ⬇️ if you think otherwise)

There are many many ways to do this, some just using vanilla JS functions such as fetch, and others using React specialty such as custom hooks. We're going to explore only some of them here, but the possibilities are endless! :innocent:

Vanilla JS

You can use fetch, axios, or many many else libraries that are useful for fetching AJAX data, but here I'm only going to demo the usage of fetch.

All JavaScript devs have probably heard of this, because fetch is a very very famous function that requires no libraries at all. Of course, you can use libraries such as isomorphic-fetch and unfetch. (But they provide similar functionality, so)

The basic syntax is like this:

let promise = fetch(url, [options]);

The Response object provides multiple promise-based methods to access the body in various formats:

This is just an overall view, so I'm not going to go too deep into the details, but you can read more about fetch on MDN.

Onwards!

SWR

SWR is a very cool React Hooks library that allows your app to fetch data through the useSWR hook.

The name "SWR" is derived from stale-while-revalidate, a HTTP cache invalidation strategy popularized by RFC 5861.

SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.

The basic API looks like this:

import useSWR from 'swr';

function Profile() {
const { data, error } = useSWR('/api/user', fetcher);

if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}

Here the fetcher object is an asynchronous function that accepts the URL/query as the parameter and then returns the data, making it easy for you to write, for example, a JSONFetcher or a GraphQLFetcher. You can define those in a separate file, for instance, fetchers.js and then import them.

// example of fetchers.js

import fetch from 'unfetch';
import { request } from 'graphql-request';
const API = 'https://api.graph.cool/simple/v1/movies';

const GraphQLFetcher = (query) => request(API, query);

const JSONFetcher = (url) => fetch(url).then((r) => r.json());

export { JSONFetcher, GraphQLFetcher };

For the detailed API and more examples, visit the repository.

react-query

react-query is another data fetching React Hooks library that has somewhat more features than SWR, including memory caching, et cetera.

An example here:

function Todos() {
const { status, data, error } = useQuery('todos', fetchTodoList);

if (status === 'loading') {
return <span>Loading...</span>;
}

if (status === 'error') {
return <span>Error: {error.message}</span>;
}

// also status === 'success', but "else" logic works, too
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}

Unfortunately, you will have to define fetchTodoList yourself rather using the indiscriminate JSONFetcher. This is a little bit inconvenient, but there's a lot of benefits too.

And also react-query has a dedicated devtools! :astonished:

That's all for client-side fetching data - now we'll move on to fetching data on the server side. (This is where Next.js specializes.)

Server-side

Next.js specializes a lot in fetching data on the server side. They provide you two built-in ways to do this: getStaticProps and getServerSideProps.

The difference between them is that getStaticProps only fetches the data once. After that one fetch, it caches the page and sends you the cached page instead of the live results. This is more useful for stuff that is not going to change often, for example, a marketing page or a blog post. getServerSideProps fetches the data every time you query it, so this is good for fetching updating user data.

getStaticProps

When using getStaticProps, the site fetches data at build time. There's also another scenario, but we'll get to that soon.

If you export an async function called getStaticProps from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps.

export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
};
}

Is how you would do this.

The context parameter, as you may have noticed, is an object containing the following keys:

You should use getStaticProps when: (this is from the Next.js documentation)

Warning: getStaticProps can only be exported from a page. You can’t export it from non-page files. Also, you must use export async function getStaticProps() {} — it will not work if you add getStaticProps as a property of the page component.

If you use getStaticProps with dynamic routes, you must also include getStaticPaths. Because you have to define the routes to build / render only once!

export async function getStaticPaths() {
return {
paths: [
{ params: { ... } }
],
fallback: true or false
};
}

The paths key decides which pages should be rendered at build time.

return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } }
],
fallback: ...
}

Would statically generate .../1 and .../2. The params are the dynamic elements inside the route.

The fallback key, a very recent feature added to Next.js 9.3. If fallback is false, then any paths not returned by getStaticPaths will result in a 404 page. If fallback is true, then the behavior of getStaticProps changes:

This is pretty useful if you have a lot of pages and you don't want to spend a whole day building your application in order to deploy it. Also, most of the time you wouldn't want to redeploy an app just because of a content addition.

So for example: if you add a page on your CMS, you will have to rebuild your entire application in order for it to be served successfully. In contrast, if you use fallback: true, when someone requests a page that’s not generated yet, the user will see the page with a loading indicator. Shortly after, getStaticProps finishes and the page will be rendered with the requested data. From now on, everyone who requests the same page will get the statically pre-rendered page. 👍

getServerSideProps

If you export an async function called getServerSideProps from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps. Looks similar to the getStaticProps API, except that you don't have to specify a getServerSidePaths - because it's all dynamic on the server side.

export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
};
}

Is how you would use it. There are 3 extra keys in the context object:

You should use getServerSideProps only if you need to pre-render a page whose data must be fetched at request time. Time to first byte (TTFB) will be slower than getStaticProp because the server must compute the result on every request, and the result cannot be cached by a CDN without extra configuration.

That's all for this article - in this article, we covered fetching the data using plain client-side JS, two React Hooks libraries, and also the specialty of Next.js, getStaticProps and getServerSideProps.

Hopefully, now you have a little more understanding about data fetching with this great React framework! 😇

If you have something to say...

You can also fire me a Twitter DM!

Click to open my Twitter: @RyanCaoDev.