Querying content

Representational State Transfer (REST) APIs, GraphQL, and Graph-Relational Object Queries (GROQ) are the most popular ways to query document data.

  • REST APIs are standard for building web APIs that use HTTP requests to retrieve data. You can retrieve all the data or predefined data structures based on the available APIs.

  • GraphQL allows you to retrieve the data that you need instead of retrieving all the data. However, you can't query data if you don't know the exact shape of the data you want.

  • GROQ is an open-source query language developed by Sanity that was designed specifically for querying structured content. GROQ allows you to query for any shape of data, providing more flexibility when working with dynamic CMS data.

GROQ's flexibility is particularly valuable when working with dynamic data, as it eliminates the need to hard code queries at the outset. For example, when selecting components on a page, GraphQL requires you to specify them ahead of time, whereas GROQ allows you to query all available data or a specific shape of data.

How it looks in action

Let's say we want to retrieve data related to a movie from an API. Here's how the query will look using REST API:

GET http://fictitious-example.com/movies/11

GraphQL:

http://fictitious-example.com/movies/

query {
  movie(id: 11) {
    _id
    title
    releaseYear
  }
}

and GROQ:

*[_id == 11]{
  _id, title, releaseYear
}

Types of content to query

No matter which method you use to query data and which CMS platform you use, the kinds of data we need are typically the same. Let's take a deeper look at each of them and how they fit into generating the front end.

Page paths

Despite having a lot of different content models, the only models we need to fetch in order to determine the page paths are the page templates. We will need to generate a page path for every unique page that exists in the CMS data. We usually don't need the entire content of the page in order to generate the paths, so we want only to return a subset of the page information (most likely only the slug) if it's possible with the query method you are using.

GROQ query:

*[_type == Page]{
  slug
}

GraphQL query:

http://another-example.com/pages/

query {
  Page() {
    slug
  }
}

The CMS data can then be used to generate your static paths. Here's an example of what that could look like in Next.js:

[...slug].js
export async function getStaticPaths() {
  const allPages = await getAllDocSlugs('Page')

  return {
    paths:
      allPages?.map((page) => {
        const slug = page.slug.split('/').filter(Boolean)

        return {
          params: {
            slug: slug
          }
        }
      }) || [],
    fallback: false
  }
}

Page data

Once the page paths are constructed, we can work on populating the page with content.

Typically, we need to return all the data associated with the page. This is challenging with GraphQL because it forces you to predefine the shape of your data.

If you have any non-templated pages, you cannot predetermine what the content authors will decide to put on the page. Rest APIs and GROQ are therefore easier and more flexible to work with in this context.

We can typically fetch a single page's data by referencing the page slug or id in the query.

GROQ query:

*[_type == Page && slug == "/about-us"]{
  ... // This will return all page data
}
*[_type == Page && slug == "/about-us"]{
  // Pick the fields you need
  title,
  description,
  heroModule,
  modules[] // This returns all your dynamic modules
}

Rest API:

GET http://another-example.com/page/189bc292-e41b-42a0-91b5-bfaa33a34af2

Here's an example of how we can pass this data to a front-end template with Next.js:

export async function getStaticProps({ params }) {
  return {
    props: {
      data: await getPageData(params.slug.join('/'))
    }
  }
}

References

References can be hard to work with because its content is usually returned as a referenceId or sometimes not returned at all.

There are many differences between CMS platforms in how they pass reference data to the user. Some will require you to define the depth of reference you would like to resolve, and others will require you to indicate specific references. Some platforms will have an option to resolve all references, but this is less common since returning large datasets will impact performance.

Global data

Some content that makes up the page should be managed globally (e.g., navigation, footer) and won't be available on your page template JSON data. You can fetch these global data elements separately and combine them with your page template data.

Here's what this could look like in Next.js:

export async function getStaticProps({ params }) {
  return {
    props: {
      data: {
        page: await getPageData(params.slug.join('/')),
        navigation: await getDataByType('navigation'),
        footer: await getDataByType('footer')
      }
    }
  }
}

Last updated

Rangle.io