Data fetching patterns for Next.js & Sanity
Tags: react, nextjs, sanity
Published: 2022-11-25 20:55:33

Next.js and Sanity are a great combination for building websites. Connecting the two technologies together is very simple: download the client library and use it in your code to fetch the data.

The problem i've found when trying to learn these tools is that there are a lot of different ways you can render the data in a Next.js app and it's not always clear what is the right one to pick for your project.

In this blog post i will explain a few patterns that i've learned over the years and explain their strengths and weaknesses.

---

If you want to skip reading the article and see the ideas in practice you can:

- Look at the [code repository](https://github.com/fibonacid/data-fetching-nextjs-sanity)
- Try the [demo](https://data-fetching-nextjs-sanity.fibonacid.net/)

---

## Server Side Rendering (SSR)

This pattern is very simple. Since Sanity provides a CDN for GROQ queries, we can fetch data on the server using `getStaticProps` without worrying too much about response times. Rendering data on the server per request and adding a cache layer on top of your database is a long established practice that has been recently chosen by Remix.

```jsx
import sanityClient from '@sanity/client';

const client = sanityClient({
  projectId: 'your-project-id',
  dataset: 'production',
  useCdn: true
})

export default function getServerSideProps({ params }) {
   const query = `[_type == 'page' && slug.current == $slug][0]`;
   const page = client.fetch(query, { slug: params.slug });
   return {
     props: {
       page
     }
   }
}

export default function Page({ page }) {
   return (
     <main>
       <h1>{page.title}</h1>
     </main>
   )
} 
```

The main benefit of this approach is that is very easy to understand. `getServerSideProps` runs on every request and the data will always come from the Sanity CDN. Updates to the dataset will become public as soon as the CDN content is replaced.

👍 Simple behaviour
❌ Slow response
👍 Errors can be spotted easily
❌ Errors take down the whole page
❌ Dataset needs to be public

---

## Incremental Static Regeneration (ISR)

To mitigate the impact of cold starts on responses we can replace `getServerSideProps` with `getStaticProps` so that page props can be stored in a CDN. To avoid presenting stale data for too long is important to keep a low `revalidate` time.

```tsx
import sanityClient from '@sanity/client';

const client = sanityClient({
  projectId: 'your-project-id',
  dataset: 'production',
  useCdn: false,
  token: process.env.SANITY_TOKEN
})

export default async function getStaticProps({ params }) {
   const query = `[_type == 'page' && slug.current == $slug && !(_id in path("drafts.**"))][0]`;
   const page = await client.fetch(query, { slug: params.slug });
   return {
     props: {
       page
     },
     revalidate: 60 // 1m
   }
}

export default async function getStaticPaths() {
  const query = `[_type == 'page' && defined(slug.current)].slug.current`;
  const pages = await client.fetch(query);
  return {
    paths: pages,
    fallback: "blocking"
  }
}

export default function Page({ page }) {
   return (
     <main>
       <h1>{page.title}</h1>
     </main>
   )
}
```

👍 Fast response on cached pages
❌ Slow response on new pages
👍 Old content is shown instead of errors
❌ Errors may remain unnoticed 
❌ Dataset needs to be public

---

## ISR + Stale While Revalidate (SWR)

A good practice to ensure fast response times without loosing freshness of your data is to revalidate the page props on the client using Javascript. Since Next.js ships a generous amount of javascript to the client we might as well use it at our advantage.

```tsx
import sanityClient from "@sanity/client";
import useSWR from "swr";

const client = sanityClient({
  projectId: 'your-project-id',
  dataset: 'production',
  useCdn: true,
});

function getPageBySlug(slug) {
  const query = `[_type == 'page' && slug.current == $slug][0]`;
  return client.fetch(query, { slug });
}

export async function getStaticProps({ params }) {
  const page = await getPageBySlug(params.slug);
  return {
    props: {
      initialData: page
    },
    revalidate: 600 // 10m 
  }
}

export default async function getStaticPaths() {
  const query = `[_type == 'page' && defined(slug.current)].slug.current`;
  const pages = await client.fetch(query);
  return {
    paths: pages,
    fallback: true
  }
}

export default Page({ initialData }) {
  const { query } = useRouter();
  const { data } = useSWR(query.slug, getPageBySlug, {
    initialData
  });
   return (
     <main>
       <h1>{data?.title}</h1>
     </main>
   )
}
```
👍 Always fast responses
👍 Data is revalidated on the client
👍 Errors can be handled on the client
❌ Dataset needs to be public