Skip to main content
A sitemap.xml file gives search engine crawlers an index of the pages on your site. In a headless Kibo storefront, your front-end application generates the sitemap, not the platform. If you only need to replace the sitemap on a Kibo-hosted storefront, see Upload a Custom Sitemap instead. This guide shows how to build a sitemap from Kibo’s Storefront Catalog APIs. It uses a sitemap index that links to sub-sitemaps for categories and for batches of products, so it covers the whole catalog even when the catalog holds hundreds of thousands of products.

Sitemap Architecture

A sitemap for a large catalog uses the index format from the Sitemap Protocol:
  • The root sitemap.xml is a sitemapindex document that lists the URL of every sub-sitemap.
  • The categories sub-sitemap is a urlset document with one entry per category page.
  • Each product batch sub-sitemap is a urlset document with the product page URLs for one slice of the catalog. Splitting products into batches keeps each sub-sitemap within the protocol’s 50,000 URL and 50 MB limit.
Example sitemap index:

Key APIs

Why Use Random Access Cursors

Paging through search results with startIndex and pageSize can miss products in a large catalog. When products are added, removed, or reordered between calls, items can be skipped or returned twice. The Random Access Cursor API returns a set of stable cursor marks in a single call. Each cursor mark covers a fixed slice of the catalog, and you can retrieve each slice independently, in any order or in parallel, without missing products.
When you call the Random Access Cursor API, pageSize can be up to 2,000. This sets how many products each cursor mark covers. For example, a catalog of 100,000 products with pageSize=2000 returns about 50 cursor marks.

Step 1: Generate the Sitemap Index

Call the Random Access Cursor API to get the cursor marks for your catalog. Each cursor mark becomes one product batch sub-sitemap.
Response:
Treat cursor marks as opaque values. Each one encodes a range of internal product IDs, not a position in the catalog, so the first mark does not normally start at 1, and the ranges are not the same width. Do not calculate product counts from them. A filter that matches nothing returns an empty cursorMarks array. Handle that case rather than publishing an index with no product sitemaps.
Cursor marks are base64 encoded and can contain / and + characters. A / inside a mark splits a URL path segment. An unfiltered sitemap does not produce these characters, but one scoped with query or filter can. Either encode each mark with encodeURIComponent when you build the index, or use a catch-all route that rejoins the path segments, as shown in the Next.js example.
Build the sitemap index from the cursor marks:

Step 2: Generate the Category Sub-Sitemap

Call the Category Tree API to get the storefront categories, then build a urlset with one entry per category.
Response (abbreviated):
items holds only the top level of the tree. Subcategories are nested under childrenCategories at every level. Walk the tree recursively; iterating over items alone publishes only your root categories. Skip categories where isDisplayed is false so that hidden categories are not published.
Use content.slug and categoryCode to build each category URL:
The URL pattern (/category/{slug}/{categoryCode} here) depends on your storefront’s routes. Adjust it to match your application.

Step 3: Generate the Product Batch Sub-Sitemaps

For each cursor mark from Step 1, call the Search Products API with the cursorMark parameter to get the products in that batch.
Response (abbreviated):
One search call does not return a whole batch. A cursor covers up to 2,000 products, but the search API returns at most 200 per call. Follow the nextCursorMark chain: pass it as cursorMark on the next call, and stop when it is null, unchanged from the cursor you sent, or the response has no items. Without this loop, the sitemap publishes at most 200 of every 2,000 products.
Use content.seoFriendlyUrl and productCode to build each product URL:
The URL pattern (/product/{seoFriendlyUrl}/{productCode} here) depends on your storefront’s routes. seoFriendlyUrl and the category slug are only populated when SEO content has been entered, so fall back to the product or category code rather than publishing undefined in a URL. Escape every value you insert into the XML. Slugs and SEO URLs are entered by merchandisers and can contain &, <, or quotes, and a single unescaped & makes the whole sitemap invalid.

GraphQL Alternative

If your storefront uses the Kibo GraphQL API, you can implement the same logic with these root query fields: Use categoriesTree, not categories, to get the hierarchy in one call. Get random access cursors:
Search products with a cursor mark, following nextCursorMark as described in Step 3:
Get the category tree:
GraphQL cannot express unlimited recursion, so select childrenCategories at least as deep as your deepest category level, or use the REST categories/tree endpoint, which returns the whole tree in one call.

Next.js Implementation Example

This example uses the Next.js Pages Router with getServerSideProps to generate the XML on each request.

Shared Helpers: src/lib/sitemap.ts

Sitemap Index: src/pages/sitemap.xml.ts

Category Sub-Sitemap: src/pages/sitemap.xml/categories.ts

Product Batch Sub-Sitemap: src/pages/sitemap.xml/productBatch/[…productBatchId].ts

Verify Coverage

Before you submit the sitemap, confirm that it covers the catalog. Call the search API once with pageSize=1&responseFields=totalCount, and compare totalCount with the total number of loc entries across all product batch sub-sitemaps. The numbers should match. A large shortfall almost always means the nextCursorMark loop is missing or stops early. Running the same logic as a one-off script against your tenant, for example with the TypeScript SDK, is a quick way to check your catalog data (in particular, whether seoFriendlyUrl and slug are populated) before you build the storefront routes.

robots.txt

Add a robots.txt file to your application’s public directory that points crawlers to the sitemap and excludes pages that should not be indexed:
Adjust the Disallow rules to match your application’s routes.

Best Practices

Performance

  • Cache sitemap responses. Cursor marks and the category tree change infrequently. Cache them at the CDN or in your application for 1 to 24 hours to reduce API calls.
  • Generate sub-sitemaps on demand. Build a product batch sub-sitemap only when a crawler requests it, rather than generating every batch in advance.
  • Request only the fields you need. Use responseFields on search calls to return only nextCursorMark, productCode, and content.seoFriendlyUrl.
  • Expect about ten calls per batch. With 2,000 products per cursor and 200 per search call, each product batch takes about ten search calls. Limit concurrency if you generate many batches in parallel.

Completeness

  • Always use random access cursors to list products. startIndex and pageSize paging alone can skip products in an active catalog.
  • Follow the nextCursorMark chain. This is the most common reason a Kibo sitemap silently leaves out most of the catalog. See Step 3.
  • Walk the full category tree. categories/tree returns root categories with subcategories nested in childrenCategories.
  • Include other page types. Consider sub-sitemaps for content pages, blog posts, and other routes in your storefront.

SEO

  • Add lastmod where you can. Use the product or category updateDate to help crawlers decide what to recrawl.
  • Don’t spend time tuning changefreq and priority. Google ignores both. lastmod is the tag that affects recrawling.
  • Submit the sitemap to Google Search Console and Bing Webmaster Tools after you deploy it.

Multiple Sites

If you run several storefronts on the same tenant, for example for different locales or brands, generate a separate sitemap for each site. Each site has its own category tree and product set, and is bound to a specific catalog within a master catalog. Scope requests to the correct site with a site-specific host or with the x-vol-site, x-vol-catalog, and x-vol-master-catalog headers. A mismatched combination returns 409 Conflict.