DEV Community

Cover image for Data fetching patterns for Next.js & Sanity
Lorenzo Rivosecchi
Lorenzo Rivosecchi

Posted on • Edited on

Data fetching patterns for Next.js & Sanity

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:


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.

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>
   )
} 
Enter fullscreen mode Exit fullscreen mode

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.

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>
   )
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘ 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.

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>
   )
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘ Always fast responses
πŸ‘ Data is revalidated on the client
πŸ‘ Errors can be handled on the client
❌ Dataset needs to be public

Top comments (0)