Next.js > Optimizing calls to DatoCMS

Optimizing calls to DatoCMS

Although the Next.js fetch API has (almost) the same interface as the regular fetch available on the browser, it is important to highlight some key differences, which might cause some surprise.

Next.js automatically does static fetches

By default, Next.js automatically does static fetches:

  • For fetch calls happening in Server Components, this means that the data will be fetched at build time, cached, and reused indefinitely on each request until your next deploy.

  • For fetch calls happening in Client Components, the cache lasts the duration of a session (which could include multiple client-side re-renders) before a full page reload.

Caching requests is generally a good idea, as it minimizes the number of requests made to DatoCMS. However, if you want to always fetch the latest data, you can mark requests as dynamic and fetch data on each request without caching.

GraphQL calls need to be manually deduped

It's quite common that multiple elements in the hierarchy of "things" that gets evaluated by Next.js to build a server response — layouts, pages, server components and generateMetadata/generateStaticParams functions — need to perform the same query.

Next.js automatically handles request deduping on GET requests — making sure that only one request is sent to the server — but since GraphQL requests use a POST HTTP action, we need to manually handle the case ourselves.

For this purpose, we can use a useful helper that React offers called cache, which memoizes the result of the passed function.

Our improved performRequest

Based on what we have just learned, we can refine our performRequest function, and make it more flexible and optimized:

import { cache } from 'react';
const dedupedFetch = cache(async (serializedInit) => {
const response = await fetch("https://graphql.datocms.com/", JSON.parse(serializedInit));
const responseBody = await response.json();
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}: ${JSON.stringify(responseBody)}`);
}
return responseBody;
})
export async function performRequest({
query,
variables = {},
includeDrafts = false,
excludeInvalid = false,
visualEditingBaseUrl,
revalidate,
}) {
const { data } = await dedupedFetch(JSON.stringify({
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NEXT_DATOCMS_API_TOKEN}`,
...(includeDrafts ? { "X-Include-Drafts": "true" } : {}),
...(excludeInvalid ? { "X-Exclude-Invalid": "true" } : {}),
...(visualEditingBaseUrl ? { "X-Visual-Editing": "vercel-v1", "X-Base-Editing-Url": visualEditingBaseUrl } : {}),
...(process.env.NEXT_DATOCMS_ENVIRONMENT ? { "X-Environment": process.env.NEXT_DATOCMS_ENVIRONMENT } : {}),
},
body: JSON.stringify({ query, variables, revalidate }),
next: { revalidate },
}));
return data;
}

This new version dedupes requests, supports all CDA header modes, and lets you control if — and for how long — you want to cache the result of the query with the revalidate option:

// cache the query result indefinitely (until next deploy)
await performRequest({ query });
// cache the query result for a maximum of 60 seconds
await performRequest({ query, revalidate: 60 });
// prevent the query result from being cached (always perform a request)
await performRequest({ query, revalidate: 0 });