Regenerate the Sitemap on a Local XM Cloud Docker Development Environment
Introducing the Sitecore XM Cloud sitemap developer utilities
Start typing to search...
In a local XM Cloud Docker development environment, the /sitemap.xml front-end route always returns an empty XML file.

When a headless site is created, the sitemap media library item that is scaffolded is empty and has a size of 0 bytes.

This makes it impossible to debug the \src\pages\api\sitemap.ts API route handler and modify it to our needs.
This article dives into the sitemap regeneration process and solutions for local XM Cloud Docker development environments.
In a hosted XM Cloud environment, this sitemap item is regenerated at the end of a publish operation of any item. An OnPublishEnd processor checks the SXA sites sitemap settings and regenerates the sitemap if enough time has elapsed since the last regeneration.
In a local Docker development environment, we cannot publish as we do not have a web database or a valid Experience Edge target. Attempting to publish results in an exception:
#Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> Sitecore.ExperienceEdge.Connector.ContentHubDelivery.Exceptions.EdgeApiException: [ExperienceEdge Publishing]: An error has occurred while publishing JobStart event 9614b999-f4dc-4fb7-ab08-75bf8a2bf429 for publishing job '638580401015070000'. Please see inner exception for more details.
---> Sitecore.Exceptions.ConfigurationException: Invalid Authority connection string. Required parameter 'url' is missing.
Thus, the sitemap is never regenerated and the media library item is never updated.
Faced with that limitation and the need to replicate a production sitemap issue in a development environment, I found creative ways to regenerate the sitemap media library item and get valuable information.
The end result is a configurable utility page that can be deployed to a local XM Cloud CM. There are a few steps to use it:
Create a generateSitemap.aspx file in your project \docker\deploy\platform folder with the code below:
 <%@ Page language="c#" %>
 <%@ Import Namespace="Microsoft.Extensions.DependencyInjection" %>
 <%@ Import Namespace="Sitecore.Abstractions" %>
 <%@ Import Namespace="Sitecore.Configuration" %>
 <%@ Import Namespace="Sitecore.Data" %>
 <%@ Import Namespace="Sitecore.Data.Items" %>
 <%@ Import Namespace="Sitecore.DependencyInjection" %>
 <%@ Import Namespace="Sitecore.Events" %>
 <%@ Import Namespace="Sitecore.Globalization" %>
 <%@ Import Namespace="Sitecore.Pipelines" %>
 <%@ Import Namespace="Sitecore.Publishing" %>
 <%@ Import Namespace="Sitecore.Sites" %>
 <%@ Import Namespace="Sitecore.Web" %>
 <%@ Import Namespace="Sitecore.XA.Foundation.SiteMetadata.EventHandlers" %>
 <%@ Import Namespace="Sitecore.XA.Foundation.SiteMetadata.Sitemap" %>
 <%@ Import Namespace="Sitecore.XA.Foundation.SiteMetadata.Pipelines.Sitemap.GenerateSitemapJob" %>
 <script runat="server">
     // IMPORTANT
     // Change the values of those variables to match the Headless SXA site name and home item you want to generate the sitemap for.
     private const string SITE_NAME = "sxastarter";
     private const string HOME_ITEM_ID = "{BEE17B56-2406-4935-A730-CFE90356BE2C}";
 </script>
 <!DOCTYPE html>
 <html>
     <head>
         <title>Sitecore XM Cloud Sitemap Developer Utilities</title>
         <meta content="C#" name="CODE_LANGUAGE">
     </head>
     <body style="font-family: sans-serif">
         <h1>Sitecore XM Cloud Sitemap Developer Utilities</h1>
         <form runat="server">
             <div>
                 Start with this button. It calls the SitemapCacheClearer.OnPublishEnd method with a mock OnPublishEnd event. The Sitecore CacheItemClearer should create one Sitemap refresh job per SXA site. It should save the sitemaps to the media library. You can track the jobs in the <a href="/sitecore/admin/Jobs.aspx" target="_blank">jobs admin page</a>.
             </div>
             <br />
             <asp:Button id="SitemapCacheClearer"
                Text="1. SitemapCacheClearer.OnPublishEnd()"
                OnClick="SitemapCacheClearer_Click"
                runat="server"/>
             <br /><br />
             <div>
                 If the above button does not generate the sitemap media library items, try these buttons in order.
             </div>
             <br />
             <div>
                 This one runs the sitemap.generateSitemapJob pipeline for the site you defined in the SITE_NAME variable. It should save the sitemap to the media library.
             </div>
             <asp:Button id="GenerateSitemapJob"
                Text="2. sitemap.generateSitemapJob pipeline"
                OnClick="GenerateSitemapJob_Click"
                runat="server"/>
             <br /><br />
             <div>
                 This one runs only the GenerateSitemap processor of the sitemap.generateSitemapJob pipeline for the site you defined in the SITE_NAME variable. It does not save the sitemap to the media library.
             </div>
             <asp:Button id="GenerateSitemap"
                Text="3. GenerateSitemap.Process()"
                OnClick="GenerateSitemap_Click"
                runat="server"/>
             <br /><br />
             <div>
                 This one runs the ItemCrawler and lists the page items it found for the HOME_ITEM_ID variable you defined. It does not save the sitemap to the media library.
             </div>
             <asp:Button id="ItemCrawler"
                Text="4. ItemCrawler.GetItems()"
                OnClick="ItemCrawler_Click"
                runat="server"/>
         </form>
         <br />
         <h2>Output</h2>
         <code>
             <%=Output %>
         </code>
     </body>
 </html>
 <script runat="server">
     private string output = "None yet. Please click one of the buttons.";
     void SitemapCacheClearer_Click(Object sender, EventArgs e)
     {
         output = GetTime() + " Starting SitemapCacheClearer.OnPublishEnd()\n\n";
         SitemapCacheClearer clearer = new SitemapCacheClearer();
         Database master = Factory.GetDatabase("master");
         List<string> targets = new List<string>(new string[] { "Edge" });
         PublishOptions options = new PublishOptions(master, master, PublishMode.SingleItem, Language.EnglishLanguage, DateTime.Now, targets);
         Publisher publisher = new Publisher(options);
         SitecoreEventArgs args = new SitecoreEventArgs("OnPublishEnd", new object[] { publisher }, new EventResult());
         clearer.OnPublishEnd(null, args);
         output += "Cancelled?: " + args.Result.Cancel.ToString() + "\n";
         if (args.Result.HasMessages)
         {
             output += "Messages:\n";
             foreach (string message in args.Result.Messages)
             {
                 output += "- " + message + "\n";
             }
         }
         if (args.Result.HasReturnValues)
         {
             output += "Return Values:\n";
             foreach (var returnValue in args.Result.ReturnValues)
             {
                 output += "- " + returnValue.ToString() + "\n";
             }
         }
         output += "\n" + GetTime() + " SitemapCacheClearer.OnPublishEnd() done. The Sitemap should have been saved to the media library.";
     }
     void GenerateSitemapJob_Click(Object sender, EventArgs e)
     {
         output = GetTime() + " Starting the sitemap.generateSitemapJob pipeline.\n\n";
         SiteContext siteContext = SiteContextFactory.GetSiteContext(SITE_NAME);
         GenerateSitemapJobArgs args = new GenerateSitemapJobArgs();
         args.SiteContext = siteContext;
         BaseCorePipelineManager pipelineManager = ServiceLocator.ServiceProvider.GetService<BaseCorePipelineManager>();
         pipelineManager.Run("sitemap.generateSitemapJob", (PipelineArgs)args);
         var messages = args.GetMessages();
         output += "Aborted?: " + args.Aborted.ToString() + "\n";
         output += "Suspended?: " + args.Suspended.ToString() + "\n";
         if (messages.Length > 0)
         {
             output += "Messages:\n";
             foreach (PipelineMessage message in messages)
             {
                 output += "- " + message.Text + "\n";
             }
         }
         output += "\nGenerated Sitemap:\n";
         foreach (string item in args.SitemapContent.Values)
         {
             output += item + "\n";
         }
         if (args.Aborted || args.Suspended)
         {
             output += "\nThe pipeline was aborted or suspended. The Sitemap might not have been saved to the media library.\n";
         } else {
             output += "\nThe Sitemap should have been saved to the media library.\n";
         }
         output += "\n" + GetTime() + " sitemap.generateSitemapJob pipeline done.";
     }
     void GenerateSitemap_Click(Object sender, EventArgs e)
     {
         output = GetTime() + " Starting GenerateSitemap.Process()\n\n";
         SiteContext siteContext = Sitecore.Sites.SiteContextFactory.GetSiteContext(SITE_NAME);
         GenerateSitemapJobArgs args = new GenerateSitemapJobArgs();
         args.SiteContext = siteContext;
         GenerateSitemap processor = new GenerateSitemap();
         processor.Process(args);
         output += "Generated Sitemap (Not saved to the media library):\n";
         foreach (string item in args.SitemapContent.Values)
         {
             output += item + "\n";
         }
         output += "\n" + GetTime() + " GenerateSitemap.Process() done.";
     }
     void ItemCrawler_Click(Object sender, EventArgs e)
     {
         output = GetTime() + " Starting ItemCrawler.GetItems()\n\n";
         Database master = Factory.GetDatabase("master");
         Item homeItem = master.GetItem(HOME_ITEM_ID);
         ItemCrawler crawler = new ItemCrawler();
         IList<Item> items = crawler.GetItems(homeItem);
         output += "Pages returned by ItemCrawler.GetItems():\n";
         foreach (Item item in items)
         {
             output += "- " + item.Name + "\n";
         }
         output += "\n" + GetTime() + " ItemCrawler.GetItems() done.";
     }
     string GetTime()
     {
         return DateTime.Now.ToLongTimeString();
     }
     private string Output {
         get {
             return Server.HtmlEncode(output).Replace(" ", " ").Replace("\n", "<br />");
         }
     }
 </script>
    The first <script> block contains 2 variables you must set to the site name and site home item ID you want the sitemap to be generated for. Ensure you set those correctly.
 <script runat="server">
     // IMPORTANT
     // Change the values of those variables to match the Headless SXA site name and home item you want to generate the sitemap for.
     private const string SITE_NAME = "sxastarter";
     private const string HOME_ITEM_ID = "{BEE17B56-2406-4935-A730-CFE90356BE2C}";
 </script>
    Save the file. The container entrypoint script will copy the saved file to the CM container wwwroot folder.
In your browser, load the https://xmcloudcm.localhost/generateSitemap.aspx page and follow the instructions.

Reload your sitemap route and enjoy a regenerated sitemap!

SITE_NAME and HOME_ITEM_ID variables values in the generateSitemap.aspx file and you saved your changes./sitecore/content/YourSiteCollection/YourSite/Settings/Sitemap)
    itemCrawler or indexCrawler
        <sitemapItemCrawler> node./sitecore/media library/Project/YourSiteCollection/YourSite/Sitemaps/YourSite/sitemap).
    Happy Sitecoring!