Walkthrough: Customizing build-time static paths in JSS Next.js apps
For very large websites with many pages, products, or articles, the static generation of all the pages might take a long time. You can reduce the build time of your static-generated application by excluding some types of pages/items from static generation.
You can achieve this by using a customized sitemap-fetcher
in the getStaticPaths
function to modify the list of page paths that are statically generated at build time.
This walkthrough describes how to:
-
Create a custom sitemap service.
-
Create custom sitemap fetchers to customize lists of static paths.
Create a custom sitemap service
You can extend the class GraphQLSitemapService
and the interface GraphQLSitemapServiceConfig
to allow you to exclude a particular item type:
-
In
src/lib
, create a new filesitemap-service.ts
. -
In the new file, import the class
GraphQLSitemapService
and the interfaceGraphQLSitemapServiceConfig
from the Sitecore Next.js SDK.RequestResponseshellimport { GraphQLSitemapService, GraphQLSitemapServiceConfig, } from '@sitecore-jss/sitecore-jss-nextjs';
-
Extend the
GraphQLSitemapServiceConfig
, add an additional option to the service configuration:RequestResponseshellexport interface ExtendedSitemapServiceConfig extends GraphQLSitemapServiceConfig { /** * Item with sub-paths to exclude */ excludeItemId?: string; }
-
Sitecore Delivery Edge requires a valid ID in the search query. For those instances where you will not use the
excludeItemId
option, you will need to provide a valid, but empty, id. Declare anemptyID
constant:RequestResponseshellconst emptyId = '{00000000-0000-0000-0000-000000000000}';
-
Extend the
GraphQLSitemapService
class and override thequery
getter such that, if you provide an ID forexcludeItemId
it will not return items of that type:RequestResponseshellexport class ExtendedSitemapService extends GraphQLSitemapService { protected get query(): string { return /* GraphQL */ ` query SitemapQuery( $rootItemId: String! $language: String! $pageSize: Int = 10 $hasLayout: String = "true" $after: String $excludeItemId: String = "${this.options.excludeItemId ?? emptyId}" ) { search( where: { AND: [ { name: "_path", value: $rootItemId, operator: CONTAINS } { name: "_path", value: $excludeItemId, operator: NCONTAINS } { name: "_language", value: $language } { name: "_hasLayout", value: $hasLayout } ] } first: $pageSize after: $after ) { total pageInfo { endCursor hasNext } results { url { path } } } } `; } constructor(public options: ExtendedSitemapServiceConfig) { super(options); }
NoteWe provide
$excludeItemId: String = "${this.options.excludeItemId ?? emptyId}"
in the query parameters , as well as the condition{ name: "_path", value: $excludeItemId, operator: NCONTAINS }
to thes earch
query.
Create custom sitemap fetchers to customize lists of static paths
With the new sitemap service in place, you can now add sitemap fetchers that suit your needs.
-
In
src/lib/sitemap-fetcher.js
, import the necessary libraries, including your new sitemap service:RequestResponseshell/* eslint-disable @typescript-eslint/no-var-requires */ import { StaticPath } from '@sitecore-jss/sitecore-jss-nextjs'; import { GetStaticPathsContext } from 'next'; import config from 'temp/config'; import { config as packageConfig } from '../../package.json'; import { ExtendedSitemapService } from './sitemap-service'; // your new service import { ItemIds } from './constants';
You can now use the refactored sitemap fetcher to customize the list of static paths for the dynamic routes in your app.
Fetch all pages, excluding some item types
You can use your new ExtendedSitemapService
to create a sitemap fetcher that excludes specific item types from the list of static paths.
For example, to fetch all pages except Products:
-
In
src/lib/sitemap-fetcher.js
, implement aRootSitemapFetcher
:RequestResponseshellexport class RootSitemapFetcher { private _graphqlSitemapService: ExtendedSitemapService; constructor() { this._graphqlSitemapService = new ExtendedSitemapService({ endpoint: config.graphQLEndpoint, apiKey: config.sitecoreApiKey, siteName: config.jssAppName, excludeItemId: ItemIds.Products, // Exclude products }); } async fetch(context?: GetStaticPathsContext): Promise<StaticPath[]> { return (process.env.EXPORT_MODE ? this._graphqlSitemapService.fetchExportSitemap(packageConfig.language) : this._graphqlSitemapService.fetchSSGSitemap(context?.locales || []) ).then((results) => { // Compensate for current bug on Delivery Edge where the root '/products' item // is being returned from the search query which excludes it ({ name: "_path", value: $productsItemId, operator: NCONTAINS }) return results.filter((value) => value.params.path[0] !== 'products'); }); } }
-
Export an instance of
RootSitemapFetcher
:RequestResponseshellexport const rootSitemapFetcher = new RootSitemapFetcher();
-
In your the file
src/pages/[[...path]].tsx
, import the instance of theRootSitemapFetcher
:RequestResponseshellimport { rootSitemapFetcher } from 'lib/sitemap-fetcher';
-
Modify / add
getStaticPaths
to use therootSitemapFetcher
:RequestResponseshellexport const getStaticPaths: GetStaticPaths = async (context) => { if (process.env.NODE_ENV !== 'development') { // Note: Next.js runs export in production mode const paths = await rootSitemapFetcher.fetch(context); return { paths, fallback: process.env.EXPORT_MODE ? false : 'blocking', }; } return { paths: [], fallback: 'blocking', }; };
Fetch only products
You can use the new ExtendedSitemapService
to fetch only a specific type of item.
For example, to fetch only Products:
-
In
src/lib/sitemap-fetcher.js
, implement aProductSitemapFetcher
, to only return product paths:RequestResponseshellexport class ProductSitemapFetcher { private _graphqlSitemapService: ExtendedSitemapService; constructor() { this._graphqlSitemapService = new ExtendedSitemapService({ endpoint: config.graphQLEndpoint, apiKey: config.sitecoreApiKey, siteName: config.jssAppName, rootItemId: ItemIds.Products, // Only products }); } async fetch(context?: GetStaticPathsContext): Promise<StaticPath[]> { return (process.env.EXPORT_MODE ? this._graphqlSitemapService.fetchExportSitemap(packageConfig.language) : this._graphqlSitemapService.fetchSSGSitemap(context?.locales || []) ).then((results) => { results.forEach((value) => { value.params.path.shift(); // Remove the leading 'products' path fragment }); return results; }); } }
-
Export an instance of the fetcher:
RequestResponseshellexport const productSitemapFetcher = new ProductSitemapFetcher();
-
In the file
src/pages/products/[[path]].tsx
, import the instance of theProductSitemapFetcher
:RequestResponseshellimport { productSitemapFetcher } from 'lib/sitemap-fetcher';
-
Modify / add the
getStaticPaths
function to use theproductSitemapFetcher
:RequestResponseshellexport const getStaticPaths: GetStaticPaths = async (context) => { if (process.env.NODE_ENV !== 'development') { // Note: Next.js runs export in production mode const paths = await productSitemapFetcher.fetch(context); return { paths, fallback: process.env.EXPORT_MODE ? false : 'blocking', }; } return { paths: [], fallback: 'blocking', }; };