Multi-site resolution

A single SitecoreAI environment can host multiple sites, each with its own content tree, hostname(s), and default language. Every query you send to the Delivery or Preview GraphQL API that touches content, such as the layout query, takes a site argument. Multi-site resolution is the process of determining, for an incoming request, which site name to use for that argument.

Your app doesn't own its URL structure. Instead, the site's content tree does. Content authors organize pages hierarchically, and that hierarchy becomes the site's URL structure. Your app accepts any incoming path with a single catch-all route handler and passes it to Sitecore as-is; if Sitecore returns null for that path (within the resolved site), the page doesn't exist and your app should serve a 404 error page.

Note

Hostnames and each host's default language are configured per site in SitecoreAI (see Manage sites). The rules for how Sitecore converts an item's name into a URL segment (casing, whitespace handling, and so on) are defined in server-side configuration owned by the team managing your Sitecore authoring environment. If your project requires custom URL patterns that differ from the item name hierarchy, this requires coordination with that team.

Site information

In SitecoreAI, each site has:

  • A name - a unique identifier for the site, passed as the site argument on layout, item, and site queries.
  • One or more hostnames - the hostname a visitor uses to reach the site. A site can be configured with multiple hostnames (for example, a staging and a production domain), and hostnames can include wildcards, such as *.example.com.
  • A default language - the language used for the site when no language is otherwise specified.

See Manage sites for how content authors configure hostnames and default languages.

Query the list of sites

Use the site query's siteInfoCollection field to retrieve every site configured in your environment.

GraphQL query:

query SiteInfoCollectionQuery {
  site {
    siteInfoCollection {
      name
      hostname
      language
      rootPath
    }
  }
}

Example response:

{
  "data": {
    "site": {
      "siteInfoCollection": [
        {
          "name": "my-site",
          "hostname": "www.example.com",
          "language": "en",
          "rootPath": "/sitecore/content/my-env/my-site"
        },
        {
          "name": "my-site-fr",
          "hostname": "www.example.fr",
          "language": "fr-FR",
          "rootPath": "/sitecore/content/my-env/my-site"
        }
      ]
    }
  }
}

You can also request a single site's information with siteInfo(site: "<name>") if you already know the site name you're looking for.

Because the site list rarely changes, retrieve it once at application startup and cache it, rather than querying it on every request. Rebuild the cache whenever you republish site configuration changes.

Resolving a request to a site

With the site list available, resolve the site for each incoming request as follows:

  1. Read the request's Host header. If your app is behind a proxy or load balancer, read the X-Forwarded-Host header.
  2. Compare the hostname against each site's configured hostname(s):
    • A site's hostname value can contain multiple hostnames, typically separated by a delimiter such as |.
    • Hostnames can include wildcard segments (*), for example *.example.com.
    • When multiple site hostnames could match the same request, prefer the most specific match over a more general one. The most specific match is usually the one that uses the fewest wildcards and has the longest literal match.
  3. If no hostname matches, fall back to a configured default site name (your app should always define one, so that local development or unrecognized hostnames still resolve to something).
  4. Use the resolved site's name as the site argument in your layout and item queries, and its language as the default language for that site (see locale resolution).
function resolveSite(hostname, siteList, defaultSiteName):
    candidates = siteList.filter(site => hostnameMatches(hostname, site.hostname))
    best = pickMostSpecific(candidates)
    return best or siteList.find(site => site.name == defaultSiteName)

Preserving the resolved site

Some parts of your app may need to remember which site was resolved beyond the initial request, most notably the Page builder, which navigates between pages using query string parameters and needs to keep using the same site across that navigation. A common pattern is to store the resolved site name in a cookie (or equivalent session state) once resolved, and prefer that stored value over hostname matching while a Page builder session is active.

Limitations

  • By default, only one site item can point to a given content start item. Configuring multiple site definitions that share a root path (for example, one per language, as shown in the example above) requires enabling language-based site resolution in your environment, and republishing afterward for the change to take effect. Different sites don't support different default languages unless this is enabled.
  • Files, as well as Sitecore and Next.js internal API routes (for example, anything under /sitecore/* or your API routes), should be excluded from site resolution.

Next steps

Continue to locale resolution to learn how the language of a request is determined and passed to Sitecore queries.

If you have suggestions for improving this article, let us know!