Image Sitemap in Sitecore

Image Sitemap in Sitecore

Images have become an integral part of websites in today's digital age, playing an important role in online marketing. However, for search engines to find these images, you must implement an Image Sitemap XML on your website.

If you're using Sitecore SXA JSS, a framework for building web applications with Sitecore Experience Platform and JavaScript, you might not be able to find an out-of-the-box solution for Image Sitemaps XML. Nonetheless, a custom solution that works seamlessly with Sitecore SXA JSS is possible.

You'll have a better understanding of how to optimize your website's images for search engines and improve your online presence by the end of this article.

  1. Inherit the _SitemapSettings base template from JSS Settings, which can be found at /sitecore/templates/Feature/Experience Accelerator/SiteMetadata/Sitemap/ SitemapSettings.

  2. You can now find a Search Engine Sitemap section in JSS settings.

    Add the external sitemap section and check: Create sitemap index instead of sitemap field

  3. So, instead of receiving URLs of items on the sitemap.xml, we receive the index file, which contains both our image sitemap URL and the original sitemap with a local prefix. Image-sitemap.xml is now missing, so we need to add a patch on processor[@type='Sitecore.XA.Feature.SiteMetadata.Pipelines.HttpRequestBegin.SitemapHandler

  4. Create a class that extends HttpRequestProcessor and overrides the Process method.

  5. Below is the code I used to achieve my requirements.

  6.     public class ImageSitemap : HttpRequestProcessor
            {
                /// <summary>
                /// Variable for storing server url to append to images.
                /// </summary>
                private static string serverUrl = string.Empty;
    
                /// <summary>
                /// Variable for storing all images on image sitemap to check for duplicates before adding.
                /// </summary>
                private static StringCollection allImages = new StringCollection();
    
                /// <summary>
                /// Gets HomeItemId.
                /// </summary>
                protected ID HomeItemId
                {
                    get { return ID.Parse("{AB606403-7EE4-443D-BDE9-E61E0F8A2EDF}"); }
                }
    
                /// <summary>
                /// Gets PageTemplateId.
                /// </summary>
                protected ID PageTemplateId
                {
                    get { return ID.Parse("{73C61864-A637-4A97-86E0-8E6F040FR852}"); }
                }
    
                public StringCollection GetImageUrlsFromItemFields(IEnumerable<Field> imageFields)
                {
                    StringCollection imagesList = new StringCollection();
                    foreach (var field in imageFields)
                    {
                        ImageField imageField = field;
                        if (imageField != null && imageField.MediaItem != null)
                        {
                            MediaItem image = new MediaItem(imageField.MediaItem);
                            string imageUrl = MediaManager.GetMediaUrl(image, new MediaUrlBuilderOptions
                            {
                                AlwaysIncludeServerUrl = true,
                                MediaLinkServerUrl = serverUrl,
                            });
    
                            /* do not add duplicate urls if image is on multiple pages */
                            if (!allImages.Contains(imageUrl))
                            {
                                imagesList.Add(imageUrl);
                                allImages.Add(imageUrl);
                            }
                        }
                    }
    
                    return imagesList;
                }
    
                public override void Process(HttpRequestArgs args)
                {
                    if (!System.IO.File.Exists(args.HttpContext.Request.PhysicalPath) &&
                        !System.IO.Directory.Exists(args.HttpContext.Request.PhysicalPath))
                    {
                        Assert.ArgumentNotNull(args, "args");
    
                        // Check if the request is of image-sitemap.xml then only allow the request to serve image sitemap.xml
                        if (args.Url == null || !args.Url.FilePath.ToLower().EndsWith("image-sitemap.xml"))
                        {
                            return;
                        }
    
                        try
                        {
                            var sitemap = new XmlDocument();
                            var declaration = sitemap.CreateXmlDeclaration("1.0", "UTF-8", null);
                            var urlset = sitemap.CreateElement("urlset");
                            urlset.SetAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");
                            urlset.SetAttribute("xmlns:image", "http://www.google.com/schemas/sitemap-image/1.1");
                            sitemap.AppendChild(declaration);
                            sitemap.AppendChild(urlset);
    
                            var index = ContentSearchManager.GetIndex($"sitecore_{Context.Database.Name}_index");
                            using (var context = index.CreateSearchContext())
                            {
                                var query = PredicateBuilder.True<SearchResultItem>();
    
                                string rootItemId = this.HomeItemId.ToString();
                                Item rootItem = Context.Database.GetItem(rootItemId);
                                UrlOptions options = LinkManager.GetDefaultUrlOptions();
                                options.AlwaysIncludeServerUrl = true;
                                serverUrl = LinkManager.GetItemUrl(rootItem, options);
    
                                string pageTemplateId = this.PageTemplateId.ToString();
                                pageTemplateId = pageTemplateId.Replace("{", string.Empty).Replace("}", string.Empty).Replace("-", string.Empty).ToLower();
    
                                Item searchFolder = rootItem != null ? rootItem : Context.Database.GetItem(Context.Site.StartPath).Parent;
                                query = query.And(x => x.Paths.Contains(searchFolder.ID));
                                query = query.And(x => x["_templates"].Contains(pageTemplateId));
                                var searchResults = context.GetQueryable<SearchResultItem>().Where(query);
    
                                if (searchResults != null)
                                {
                                    foreach (var resultItem in searchResults)
                                    {
                                        /* Get page item and page item Url */
                                        Item item = resultItem.GetItem();
                                        string itemUrl = LinkManager.GetItemUrl(item, options);
    
                                        /* Get media Urls for images selected on data sources used in renderings on a given page */
                                        string deviceName = "Default".ToLower();
                                        StringCollection images = new StringCollection();
                                        DeviceItem deviceItem = item.Database.Resources.Devices.GetAll().Where(d => d.Name.ToLower() == deviceName).First();
                                        if (deviceItem != null)
                                        {
                                            RenderingReference[] renderings = item.Visualization.GetRenderings(deviceItem, false);
                                            if (renderings != null)
                                            {
                                                /* iterate through all renderings on a page */
                                                foreach (RenderingReference renderingref in renderings)
                                                {
                                                    /* get rendering datasource item */
                                                    Item datasource = item.Database.GetItem(renderingref.Settings.DataSource);
    
                                                    /* Visualization.GetRenderings() returns null for SXA local: data sources, so need to get local data source path in those cases */
                                                    if (string.IsNullOrEmpty(renderingref.Settings.DataSource) && !string.IsNullOrEmpty(item.Fields[Sitecore.FieldIDs.FinalLayoutField].Value))
                                                    {
                                                        XmlDocument xmlDoc = new XmlDocument();
                                                        xmlDoc.LoadXml(item.Fields[Sitecore.FieldIDs.FinalLayoutField].Value);
    
                                                        string xpath = string.Format("r/d/r[@uid='{0}']", renderingref.UniqueId.ToString());
                                                        XmlNode node = xmlDoc.SelectSingleNode(xpath);
                                                        if (node != null && node.Attributes["s:ds"] != null)
                                                        {
                                                            string fullPath = node.Attributes["s:ds"].Value.Replace("local:", item.Paths.FullPath);
                                                            datasource = item.Database.GetItem(fullPath);
                                                        }
                                                    }
    
                                                    if (datasource != null)
                                                    {
                                                        /* get urls from all Image fields on the datasource item */
                                                        images.AddRange(this.GetImageUrlsFromItemFields(datasource.Fields.Where(f => f.TypeKey == "image")).Cast<string>().ToArray());
    
                                                        /* get urls from all Image fields on Child items of the datasource item, as in this case ChildItemContentsResolver is used */
                                                        if (datasource.HasChildren)
                                                        {
                                                            foreach (Item child in datasource.Children)
                                                            {
                                                                images.AddRange(this.GetImageUrlsFromItemFields(child.Fields.Where(f => f.TypeKey == "image")).Cast<string>().ToArray());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
    
                                        if (images != null && images.Count > 0)
                                        {
                                            XmlElement urlElement = sitemap.CreateElement("url");
                                            XmlElement locElement = sitemap.CreateElement("loc");
                                            locElement.InnerText = itemUrl;
                                            urlElement.AppendChild(locElement);
                                            urlset.AppendChild(urlElement);
    
                                            foreach (string image in images)
                                            {
                                                XmlElement imageElement = sitemap.CreateElement("image", "image", "http://www.google.com/schemas/sitemap-image/1.1");
                                                XmlElement imageLoc = sitemap.CreateElement("image", "loc", "http://www.google.com/schemas/sitemap-image/1.1");
                                                imageLoc.InnerText = image;
                                                imageElement.AppendChild(imageLoc);
                                                urlElement.AppendChild(imageElement);
                                            }
                                        }
                                    }
                                }
                            }
    
                            allImages.Clear();
    
                            // Write XML Response for Sitemap.
                            var response = HttpContext.Current.Response;
                            response.ContentType = "application/xml";
                            response.Write(sitemap.InnerXml);
                            HttpContext.Current.ApplicationInstance.CompleteRequest();
                            args.AbortPipeline();
    
                            // Response Ends Here
                        }
                        catch (Exception ex)
                        {
                            Log.Error("Error - image-sitemap.xml.", ex, this);
                        }
                    }
                }
            }
    

Output:

When you visit example.com/sitemap.xml

and example.com/image-sitemap.xml shows the output like the below:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
  <url>
    <loc>https://example.com/sample1.html</loc>
    <image:image>
      <image:loc>https://example.com/image.jpg</image:loc>
    </image:image>
    <image:image>
      <image:loc>https://example.com/photo.jpg</image:loc>
    </image:image>
  </url>
  <url>
    <loc>https://example.com/sample2.html</loc>
    <image:image>
      <image:loc>https://example.com/picture.jpg</image:loc>
    </image:image>
  </url>
</urlset>