Building Sitemaps using Sitecore Headless Services / NextJs  GraphQL

Building Sitemaps using Sitecore Headless Services / NextJs GraphQL

Recently I have been working on a 10.2 Sitecore JSS (NextJS/React) using SXA multisite environment. SXA is best for adding Tenants and Sites and managing the Content Tree with Local Data and site-specific Media Library and Data Template management.

Lately, we have had to develop sitemaps for our multi-site solution so I've decided to share the Sitemaps solution I put together using the power of Next Js and GraphQL. This article assumes you have set up your JSS application already.

I have referred the XM cloud repo to find a starting solution:-

Sitecore XM Cloud Repo

The above link if you refer has already a Sitemap-fetcher plugin created by the great Sitecore team which is using the next sitemap plugin hosted on npm

npmjs.com/package/next-sitemap

I am sharing the GitHub code repo for the same next-sitemap below

github.com/iamvishnusankar/next-sitemap

Coming back to Sitecore XM Cloud Repo, I found that there is a service file with the name graphql-sitemap-service.ts which has the below code

import { GraphQLSitemapService } from '@sitecore-jss/sitecore-jss-nextjs'; 
import config from 'temp/config'; 
import { SitemapFetcherPlugin } from '..'; 
import { GetStaticPathsContext } from 'next'; 
import { StaticPath, constants } from '@sitecore-jss/sitecore-jss-nextjs';

class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin { _graphqlSitemapService: GraphQLSitemapService;

constructor() { this._graphqlSitemapService = new GraphQLSitemapService({ 
endpoint: config.graphQLEndpoint, 
apiKey: config.sitecoreApiKey, 
siteName: config.jssAppName, 
rootItemId: '36CAD6CF-D418-43CA-BC57-656E44069116', 
}); }

async exec(context?: GetStaticPathsContext): Promise<StaticPath[]> { if (process.env.EXPORT_MODE) { 
// Disconnected Export mode 
if (process.env.JSS_MODE !== constants.JSS_MODE.DISCONNECTED) { return this._graphqlSitemapService.fetchExportSitemap(config.defaultLanguage); } } return this._graphqlSitemapService.fetchSSGSitemap(context?.locales || []); } }

export const graphqlSitemapServicePlugin = new GraphqlSitemapServicePlugin(); `>

This file takes the endpoint, site name, api key and root item Id of the site which we pass from Next JS and will generate the Sitemaps for your site.

Again, if you want to override the behavior and lets say want to create sitemaps file per entity like products, articles you can do so.

I am giving an example of Sitemap file I want to create for only items with template Id Article,

import { GetServerSideProps } from 'next';
import { ArticlesSitemapTemplatesId } from '../../../../../lib/sitemap-fetcher/constants';
import { GetServerSideSitemaps } from '../../../../../lib/sitemap-fetcher/services/sitemap.service';
import config from '../../../../../temp/config';
import { buildGraphQLSitemapService } from '../../../../../lib/sitemap-fetcher/services/GraphQLSitemapServiceFactory';

export const getServerSideProps: GetServerSideProps = async (ctx) =>
  GetServerSideSitemaps(
    ctx,
    buildGraphQLSitemapService(ctx, {
      endpoint: config.graphQLEndpoint,
      apiKey: config.sitecoreApiKey,
      siteName: config.jssAppName,
      templates: ArticlesSitemapTemplatesId,
    })
  );

// Default export to prevent next.js errors
// eslint-disable-next-line @typescript-eslint/no-empty-function
export default function SitemapArticles() {}

Here as we see we have created our own wrapper functions and passing a param as article template Id which calls our own Sitemap service.

If we open / override the graphql sitemap service, we can see the code therein and change the graphql query as per our requirements

const defaultQuery = /* GraphQL */ `
  query SitemapQuery(
    $rootItemId: String!
    $language: String!
    $pageSize: Int = 10
    $templates: String
    $hasLayout: String = "true"
    $after: String
  ) {
    search(
      where: {
        AND: [
          { name: "_path", value: $rootItemId, operator: CONTAINS }
          { name: "_language", value: $language }
          { name: "_hasLayout", value: $hasLayout }
          { name: "SitemapsFile", value: $templates }
          { name: "ExcludeSeoSitemaps", value: "0" }
        ]
      }
      first: $pageSize
      after: $after
    ) {
      total
      pageInfo {
        endCursor
        hasNext
      }
      results {
        url {
          path
        }
        sitemapupdateddatetime: field(name: "SitemapUpdated") {
          value
        }
        priority: field(name: "Priority") {
          ... on TextField {
            value
              }
        }
      }
    }
  }
`;

Again it depends on whether you want to generate Sitemaps Client side or Server side on Next JS Rendering host and which will ask further to change your approach accordingly.

Happy Learning!