Utilizing Next.js API Routes for Sitecore XM Cloud GraphQL Queries

Exploring the integration of Next.js with Sitecore XM Cloud for enhanced GraphQL data fetching capabilities

March 24, 2024

By John Flores

Sitecore XM Cloud has a tool for grabbing data outside of the page layout using Sitecore GraphQL API. You can create GraphQL queries that can basically grab items from Sitecore ranging from sites, pages down to components and data sources. You can read more about Useful Sitecore GraphQL queries here.

There will be scenarios where the data passed don’t actually change that often and because of that we would want to cache this data to help speed up page loading. This is where Next.js API Routes will come into the picture. Next.js has introduced a feature which allows creating API routes within Next.js without having to rely on backend integration. The advantages of using it in our situation are the following:

  • Allow masking of private endpoints which we don’t want end users to see.
  • Single source of truth of data.
  • Allows the complexity of using Sitecore GraphQL queries in the API, allowing developers to easily convert it to fetch functions which are more common to understand.

GraphQL Query Setup

To start off, create a function that accepts a datasource and language since these two are the most variable and so we can reuse this function for different datasources. Let’s call this function componentQuery. Two things we’ll need to get this to work are the datasource which is a string and the language of the data you want. The code will look like this.

export const **componentQuery** = async (datasource: string, language: string) => {};

We will then add some setup code for GraphQL in order for it to connect to Sitecore. Similar to how you setup GraphQL queries in Next.js, the updated code will look like the following.


export const **componentQuery** = async (datasource: string, language: string) => {
    const graphQLClient = new GraphQLClient(config.graphQLEndpoint);
    graphQLClient.setHeader('sc_apikey', config.sitecoreApiKey);
};

Writing Your GraphQL Query

A good example we work on is a navigation query that grabs all the pages under a certain datasource. You would expect this sort of data to be less volatile and only edited a couple of times which we leverage the power of caching on. Let’s write up the query string we are using to grab the data using GraphQL.

const query = gql`
    query {
      item(path: "ADD_DATASOURCE_HERE", language: "ADD_LANGUAGE_HERE") {
        children(includeTemplateIDs: ["ADD_PAGE_TEMPLATE_ID_HERE"]) {
          results {
            ... on LandingPage {
              title {
                value
              }
              url {
                path
              }
            }
          }
        }
      }
    }
  `;

Using some of the parameters we have in our function let’s improve this set of code. It’ll help future developers from debugging and enhancing this piece of code if needed. We just have to add in the variables and introduce some new variables which can help make this piece of code more generic and reusable.

const datasource = datasource;
const language = language;
const pageID = "{ADD_THE_TEMPLATE_ID_HERE}";
const query = gql`
    query {
      item(path: "${datasource}", language: "${language}") {
        children(includeTemplateIDs: ["${pageID}"]) {
          results {
            ... on LandingPage {
              title {
                value
              }
              url {
                path
              }
            }
          }
        }
      }
    }
  `;

Completing the Data Fetch Function

Now that we have our query, let’s add it in the function we’ve created earlier.

export const **componentQuery** = async (datasource: string, language: string) => {
    const graphQLClient = new GraphQLClient(config.graphQLEndpoint);
    graphQLClient.setHeader('sc_apikey', config.sitecoreApiKey);
    const datasource = datasource;
        const language = language;
        const pageID = "{ADD_THE_TEMPLATE_ID_HERE}";
    <span class="hljs-keyword">const</span> query = gql`
    query {
      item(path: <span class="hljs-string">"<span class="hljs-subst">${datasource}</span>"</span>, language: <span class="hljs-string">"<span class="hljs-subst">${language}</span>"</span>) {
        children(includeTemplateIDs: [<span class="hljs-string">"<span class="hljs-subst">${pageID}</span>"</span>]) {
          results {
            ... on Page {
              title {
                value
              }
              url {
                path
              }
            }
          }
        }
      }
    }

`; };

Lastly let’s add in the request call and return whatever we got from it. Let’s also complete this by adding in any imports that we might need.

import { gql, GraphQLClient } from 'graphql-request';
import config from 'temp/config';

export const componentQuery = async (datasource: string, language: string) => { const graphQLClient = new GraphQLClient(config.graphQLEndpoint); graphQLClient.setHeader('sc_apikey', config.sitecoreApiKey); const datasource = datasource; const language = language; const pageID = "{ADD_THE_TEMPLATE_ID_HERE}";

    <span class="hljs-keyword">const</span> query = gql`
    query {
      item(path: <span class="hljs-string">"<span class="hljs-subst">${datasource}</span>"</span>, language: <span class="hljs-string">"<span class="hljs-subst">${language}</span>"</span>) {
        children(includeTemplateIDs: [<span class="hljs-string">"<span class="hljs-subst">${pageID}</span>"</span>]) {
          results {
            ... on Page {
              title {
                value
              }
              url {
                path
              }
            }
          }
        }
      }
    }
  `;
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> graphQLClient.request(query);
  <span class="hljs-keyword">return</span> data;

};

We’ve completed our data fetching function, next on our list is creating our API route in Next.js that uses this function.

Creating Our Next.js API Route

So the only requirement we need to make a page an API route is add the file inside this path.

src/pages/api/{YOUR_API_PATH_HERE}/index.tsx

You can then be able to access the API like so, http://localhost:3000/api/REST_OF_FILE_PATH . I would prefer first categorizing the API based on the version, then the purpose of the API. This will be the route I’m expecting to create http://localhost:3000/api/v1/navigation . Let’s add in the initial set of code we’ll need for an API route. We’re also expecting it to be a GET function with a query parameter of an id. This will make the cache be different for every id.

import { **componentQuery** } from 'PATH_OF_YOUR_FUNCTION_HERE';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) { const query = req.query; const { id } = query; if (req.method === 'GET') { // CODE FOR FETCHING } else { return res.status(400).json({ message: 'Invalid method used.' }); } }

So you can access your query parameters using the req parameter Then we add in a condition that returns an error if you are using a different method aside from GET. We then proceed to add in the rest of the code.

import { **componentQuery** } from 'PATH_OF_YOUR_FUNCTION_HERE';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) { const query = req.query; const { id } = query; if (req.method === 'GET') { try { const data = await secondaryNavQuery(id as string, 'en'); res.status(200).json({ data }); } catch (e) { res.status(500).json({ message: 'Error' }); } } else { return res.status(400).json({ message: 'Invalid method used.' }); } }

The code is a standard promise call of the function we’ve created. To continue optimizing this we can leverage on adding in his piece of code before returning the data. This is to cache the data and help improve speed since this data we’ve expecting to be the same for a long period of time.

res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=300');

Add it after the data is received or just before passing in the data.

import { **componentQuery** } from 'PATH_OF_YOUR_FUNCTION_HERE';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) { const query = req.query; const { id } = query; if (req.method === 'GET') { try { const data = await secondaryNavQuery(id as string, 'en'); res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate=300'); res.status(200).json({ data }); } catch (e) { res.status(500).json({ message: 'Error' }); } } else { return res.status(400).json({ message: 'Invalid method used.' }); } }

Perfect! We can then call the endpoint http://localhost:3000/api/v1/navigation?id=ADD_ID_HERE. There’s a lot of cool things you can do using the Next.js API Routes and this is one of them. It’s a powerful feature you can test out and experiment on which can help create more secure routes without exposing the real route. You can also add in your own set of caching depending on what we’re expecting from the data which can help speed up data fetching. You can read more on this on the resources below.



John Headshot

John Flores

Front-End Developer

John is a Senior Front-End Developer who is passionate about design and development. Outside of work, John has wide range of hobbies, from his plant collection, being a dog daddy, and a foodie.

Second CTA Ogilvy's Legacy

Today, David Ogilvy's influence can still be felt in the world of advertising.

Ogilvy's Influence Example
Emphasis on research Market research is a crucial part of any successful advertising campaign
Focus on headlines A strong headline can make the difference between an ad that is noticed and one that is ignored
Use of visuals Compelling images and graphics are essential for capturing audience attention