Why you need both
- sitemap.xml lists every page you want search engines to find, with when it last changed.
- robots.txt tells crawlers what not to crawl, such as the
/umbracobackoffice, and where your sitemap is.
The code below is a simplified version of what powers umbraco.ninja's own sitemap.xml and robots.txt.
Step 1: Let editors exclude pages
Add a True/false property called Hide from sitemap (alias hideFromSitemap) to your SEO composition. Thank-you pages, search results and test pages shouldn't appear.
Step 2: The controller
using System.Xml.Linq;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace MySite.Controllers;
public class SitemapController(
IDocumentNavigationQueryService navigation,
IPublishedContentCache contentCache,
IPublishedUrlProvider urlProvider) : Controller
{
private static readonly XNamespace Ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
[HttpGet("sitemap.xml")]
[ResponseCache(Duration = 3600)]
public IActionResult Sitemap()
{
navigation.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
IEnumerable<IPublishedContent> roots = rootKeys
.Select(key => contentCache.GetById(key))
.OfType<IPublishedContent>();
var urls = roots
.SelectMany(root => root.DescendantsOrSelf())
.Where(page => page.TemplateId > 0 || page.IsVisible())
.Where(page => page.Value<bool>("hideFromSitemap") == false)
.Select(page => new XElement(Ns + "url",
new XElement(Ns + "loc", urlProvider.GetUrl(page, UrlMode.Absolute)),
new XElement(Ns + "lastmod", page.UpdateDate.ToString("yyyy-MM-dd"))));
var document = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement(Ns + "urlset", urls));
return Content(document.Declaration + "\n" + document.Root, "application/xml");
}
[HttpGet("robots.txt")]
public IActionResult Robots()
{
string host = $"{Request.Scheme}://{Request.Host}";
return Content($"User-agent: *\nDisallow: /umbraco/\n\nSitemap: {host}/sitemap.xml\n", "text/plain");
}
}- In Umbraco 18, the top of the content tree comes from
IDocumentNavigationQueryService.TryGetRootKeys. The oldGetAtRoot()no longer exists on the content cache. UrlMode.Absolutegives full URLs, which sitemaps require.
Step 3: Make sure the routes are mapped
Attribute-routed controllers need MapControllers(). Umbraco's endpoints already map them in most projects, but if /sitemap.xml returns 404, add this after UseUmbraco():
app.MapControllers();Step 4: Check it and submit it
- Visit
/sitemap.xmland/robots.txton your site. - In Google Search Console, open Sitemaps and submit
https://example.com/sitemap.xml. Do the same in Bing Webmaster Tools.
Big sites
Sitemaps are limited to 50,000 URLs. Beyond that, split them into several sitemaps listed in a sitemap index, for example one per section or language.
Need a hand?
umbraco.ninja has more than 20 years of Umbraco experience. If you'd rather not do this yourself, we'll set it up, test it and hand it over.
Get a fixed-price quote