Experience Sitecore ! | All posts tagged 'XM Cloud'

Experience Sitecore !

More than 300 articles about the best DXP by Martin Miles

A full guide to creating a multi-language sites with Sitecore XM Cloud and Next.js

Historically, it was quite challenging to add custom languages to the sitecore, as it was dependent on the cultures registered in the .net framework on the OS level. Of course, there were a few workarounds like registering the custom culture on Windows, but it only added other challenges for scenarios such as having more than one Content Delivery Server.

Luckily, both XM Cloud and SXA changed the way we deal with it, not to mention retiring CD servers. I am going to show the entire walkthrough, in action – everything you need to do on the CM side of things and your Next.js head application, on an example of a custom component. So, here we go!

In the beginning, I only had a basic website running in a multi-site SXA-based environment. Because of that it benefits from a responsive Header component with navigation items. It will be a good place to locate a language dropdown selector, as that’s where users traditionally expect it to be. But first, let’s add languages into the system as English is the default and the only one I have so far. Since I live in North America, I am going to add two most popular languages – French and Spanish, as commonly spoken in Quebec and Mexico correspondingly.

Adding Languages

In XM Cloud, languages are located under /sitecore/system/Languages folder. If a language is not present there, you won’t be able to use it, which is my case. I like the functional language selector dialog provided by the system:

Adding language into the system

Pro tip: don’t forget to add languages into serialization.

After the language lands into a system, we can add it to a specific website. I enjoy plenty of scaffolding in SXA is based on SPE scripts and here’s a case. To add the site language, choose Scripts from a context menu, then select Add Site Language:

02

Then specify the language of choice, as well as some parameters, including the language fallback option to the defaults.

03

In XM Cloud you can find a new Custom Culture section on the language item, which has two important fields:

  • Base ISO Culture Code: the base language you want to use, for example: en
  • Fallback Region Display Name: display name that can be used in the content editor or in the Sitecore pages.

Now both the system and website have these new languages. The next step would be introducing a drop-down language selector, at the top right corner of a header.

Unlike the traditional non-headless versions of XP/XM platforms, XM Cloud is fully headless and serves the entire layout of a page item with all the components via Experience Edge, or a local GraphQL endpoint running on a local CM container with the same schema. Here’s what it looks like in GraphQL IDE Playground:

04

There are two parts to it: context which contains Sitecore-context useful information, such as path, site, editing mode, current language, and route with the entire layout of placeholders, components, and field values. Since the language selector is a part of the header and is shown on every single page, that would be really great (and logical) to provide the list of available languages to feed this component with a context. How can we achieve that?

The good news is pretty doable through Platform customization by extending getLayoutServiceContextpipeline and adding ContextExtension processor:

<?xml version="1.0"?>
<configuration
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:set="http://www.sitecore.net/xmlconfig/set/"
	xmlns:role="http://www.sitecore.net/xmlconfig/role/">
	<sitecore>
		<pipelines>
			<groupgroupName="layoutService">
				<pipelines>
					<getLayoutServiceContext>
						<processortype="JumpStart.Pipelines.ContextExtension, JumpStart"resolve="true"/>
					</getLayoutServiceContext>
				</pipelines>
			</group>
		</pipelines>
	</sitecore>
</configuration>

and the implementation:

using System.Collections.Generic;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.JavaScriptServices.Configuration;
using Sitecore.LayoutService.ItemRendering.Pipelines.GetLayoutServiceContext;
namespace JumpStart.Pipelines
{
    public class ContextExtension : Sitecore.JavaScriptServices.ViewEngine.LayoutService.Pipelines.
            GetLayoutServiceContext.JssGetLayoutServiceContextProcessor
    {
        public ContextExtension(IConfigurationResolver configurationResolver) : base(configurationResolver)
        {
        }

        protected override void DoProcess(GetLayoutServiceContextArgs args, AppConfiguration application)
        {
            Assert.ArgumentNotNull(args, "args");
            var langVersions = new List<Language>();
            Item tempItem = Sitecore.Context.Item;
            foreach (var itemLanguage in tempItem.Languages)
            {
                var item = tempItem.Database.GetItem(tempItem.ID, itemLanguage);
                if (item.Versions.Count > 0 || item.IsFallback)
                {
                    langVersions.Add(itemLanguage);
                }
            }
            args.ContextData.Add("Languages", langVersions);
        }
    }
}

To make this code work we need to reference Sitecore.JavaScriptServices package. There was an issue that occurred after adding a package: the compiler errored out demanding to specify the exact version number of this package. It should be done at packages.props at the root of a mono repository as below:

<PackageReference Update="Sitecore.JavaScriptServices.ViewEngine" Version="21.0.583" />

After deploying, I am receiving site languages as a part of Sitecore context object for every single page:

08

If for some reason you do not see language in the graphQL output, but the one exists in both system and your site – make sure it has language fallback specified:

05

You also need to configure language fallback on a system, as per the official documentation. I ended up with this config patch:

<?xml version="1.0"?>
<configuration
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:set="http://www.sitecore.net/xmlconfig/set/"
	xmlns:role="http://www.sitecore.net/xmlconfig/role/">
	<sitecore>
		<settings>
			<settingname="ExperienceEdge.EnableItemLanguageFallback"value="true"/>
			<settingname="ExperienceEdge.EnableFieldLanguageFallback"value="true"/>
		</settings>
		<sites>
			<sitename="shell">
				<patch:attributename="contentStartItem">/Jumpstart/Jumpstart/Home
				</patch:attribute>
				<patch:attributename="enableItemLanguageFallback">true
				</patch:attribute>
				<patch:attributename="enableFieldLanguageFallback">true
				</patch:attribute>
			</site>
			<sitename="website">
				<patch:attributename="enableItemLanguageFallback">true
				</patch:attribute>
				<patch:attributename="enableFieldLanguageFallback">true
				</patch:attribute>
			</site>
		</sites>
	</sitecore>
</configuration>

I also followed the documentation and configured language fallback on a page item level at the template’s standard values:

10

Now when I try to navigate to my page by typing https://jumpstart.localhost/es-MX/Demo, browser shows me 404. Why so?

07

This happens because despite we added languages in XM Cloud CM and even specified the fallback, the next.js head application knows nothing about these languages and cannot serve the corresponding routes. The good news is that the framework easily supports that by just adding served languages into next.config.js of a relevant JSS application:

i18n:{
  locales:[
    'en',
    'fr-CA',
    'es-MX',
],
  defaultLocale: jssConfig.defaultLanguage,
  localeDetection:false,
}

After the JSS app restarts and upon refreshing a page, there’s no more 404 error. If done right, you might already see a header.

But in my case the page was blank: no error, but no header. The reason for this is pretty obvious – since I benefit from reusable components by a truly multisite architecture of SXA, my header component belongs to a Partial Layout, which in turn belongs to a Shared website. Guess what? It does not have installed languages, so need to repeat language installation for the Shared site as well. Once done – all works as expected and you see the header from a shared site’s partial layout:

09

Dropdown Language Selector

Now, I decided to implement a dropdown Language Selector component at the top right corner of a header that picks up all the available languages from the context and allows switching. This will look something like the below:

import { SitecoreContextValue } from '@sitecore-jss/sitecore-jss-nextjs';
import { useRouter } from 'next/router';
import { ParsedUrlQueryInput } from 'querystring';
import { useEffect, useState } from 'react';
import { ComponentProps } from 'lib/component-props';
import styles from './LanguageSelector.module.css';

export type HeaderContentProps = ComponentProps & {
    pathname?: string;
    asPath?: string;
    query?: string | ParsedUrlQueryInput;
    sitecoreContext: SitecoreContextValue;
};
const LanguageSelector = (props: HeaderContentProps): JSX.Element => {
    const router = useRouter();
    const [languageLabels, setLanguageLabels] = useState([]);
    const sxaStyles = `${props.params?.styles || ''}`;
    const languageNames = new Intl.DisplayNames(['en'], { type: 'language' });
    const languageList = props.sitecoreContext['Languages'] as NodeJS.Dict<string | string>[];
    useEffect(() => {
        const labels = languageList.map((language) => languageNames.of(language['Name']));
        setLanguageLabels(labels);
    const changeLanguage = (lang: string) => {
    }, []);
        if (props.pathname && props.asPath && props.query) {
            router.push(
                {
                    pathname: props.pathname,
                    query: props.query,
                },
                props.asPath,
                {
                    locale: lang,
                    shallow: false,
                }
            );
        }
    };
    const languageSelector = languageList && languageLabels.length > 0 && (
        <select
            onChange={(e) => changeLanguage(e.currentTarget.value)}
            className="languagePicker"
            value={props.sitecoreContext.language}
        >
            {languageList.map((language, index) => (
                <option
                    key={index}
                    value={language['Name']}
                    label={languageLabels[index]}
                    className="languageItem"
                >
                    {languageNames.of(language['Name'])}
                </option>
            ))}
        </select>
    );
    return (
        <>
            <div className={`${styles.selector}${sxaStyles}`}>{languageSelector}</div>
        </>
    );
};
export default LanguageSelector;

Since I made the header responsive with a “hamburger” menu seen on mobiles, I am also referencing responsive styles for this component:

.selector {
    float: right;
    position: relative;
    top: 13px;
    right: 40px;
}

@mediascreenand (max-width: 600px) {
    .selector {
        right: 0px;
    }
}

Now it can be used from the header as:

<LanguageSelector pathname={pathname} asPath={asPath} query={query} sitecoreContext={sitecoreContext}{...props} />

and it indeed looks and works well:

11

switching to Spanish correctly leverages next.js for switching the language context and changing the URL:

12

Now, let’s progress with a multi-language website by adding a demo component and playing it over.

Adding a Component

For the sake of the experiment, I decided to gith something basic – an extended Rich Text component that in addition to a datasource also receives background color from Rendering Parameters. There are 3 lines with it:

  • the top line in bold is always static and is just a hardcoded name of the component, should not be translated
  • the middle line is internal to the component rendering, therefore I cannot take it from the datasource, so use Dictionary instead
  • the bottom one is the only line editable in Pages/EE and comes from the datasource item, the same as with the original RichText

Here’s what it looks like on a page:

13

And here’s its code (ColorRichText.tsx):

import React from 'react';
import { Field, RichText as JssRichText } from '@sitecore-jss/sitecore-jss-nextjs';
import { useI18n } from 'next-localization';
interface Fields {
    Text: Field<string>;
}
export type RichTextProps = {
    params: { [key: string]: string };
    fields: Fields;
};
export const Default = (props: RichTextProps): JSX.Element => {
    const text = props.fields ? (
        <JssRichText field={props.fields.Text} />
    ) : (
        <span className="is-empty-hint">Rich text</span>
    );
    const id = props.params.RenderingIdentifier;
    const { t } = useI18n();
    return (
        <div
            className={`component rich-text ${props.params.styles?.trimEnd()}`}
            id={id ? id : undefined}
        >
            <div className="component-content">
                <h4>Rich Text with Background Color from Rendering Parameters</h4>
                <span>{t('Rendering Parameters') || 'fallback content also seen in EE'}: </span>
                {text}
                <style jsx>{`
          .component-content {
            background-color: ${props.params.textColor
                        ? props.params.textColor?.trimEnd()
                        : '#FFF'};
          }
        `}</style>
            </div>
        </div>
    );
};

What is also special about this component, I am using I18n for reaching out to Dictionary items, see these lines:

import{ useI18n } from 'next-localization';
const{ t } = useI18n();
<span>{t('Rendering Parameters') || 'fallback content, it is also seen in EE when defaults not configured'}: </span>

Next, create a version of each language for the datasource item and provide the localized content. You have to create at least one version per language to avoid falling back to the default language – English. The same also applies to the dictionary item:

14

The result looks as below:

16

15

Rendering Parameters

Now, you’ve probably noticed that the English version of the component has a yellow background. That comes from rendering parameters in action configured per component so that editors can choose a desired background color from a dropdown (of course, it is a very oversimplified example for demo purposes).

22

What is interesting in localization is that you can also customize Rendering Parameters per language (or keep them shared by the language fallback).

Rendering Parameters are a bit tricky, as they are stored in the __Renderings and __Final Renderings fields of a page item (for Shared and Versioned layouts correspondingly), derived from Standard template (/sitecore/Templates/System/Templates/Standard template). That means when you come to a page with a language fallback, you cannot specify Rendering Parameters for that language unless you create a version of the entire page. Both Content Editor and EE will prevent you from doing that while there is a language fallback for the page item:

Content Editor

Experience Editor

Creating a new language version can be very excessive effort as by default it will make you set up the entire page layout add components (again) and re-assigning all the datasources for that version. It could be simplified by copying the desired layout from the versioned __Final Renderings field to the shared __Renderings field, so that each time you create a new language version for a page item, you “inherit” from that shared design and not create it from scratch, however, that approach also has some caveats – you may find some discussions around that (here and there).

In any case, we’ve got the desired result:

19

English version

20

French version

21

Spanish version

Hope you find this article helpful!

The correct way of creating own components with XM Cloud

I occasionally evidence folks creating components in XM Cloud incorrectly. Therefore I decided to create and share this guidance with you.

So, there are five steps involved in creating your own component:

  1. Create an SXA module that will serve as a pluggable container for all the necessary assets for your components, if not done yet.
  2. The easiest way to create a component is to clone a mostly matching existing one. If you need to rely on datasource items, clone the one that already leverages datasource. SPE scaffolding script will do the rest of the job for you. Make sure you assign a newly created component to a module from Step 1 above.
  3. Now, having a module with component(s),  you need to make it visible for your website, but adding a module to a chosen site. This empowers your site to see a corresponding section in the toolbox with newly created component(s) for you to use straight away, for both Experience Editor and Pages.
  4. You need to ensure the Component Name field is referencing the name of corresponding TSX codebase files as /src/<jss_app>/src/components/<your component>.tsx or one more level down within a folder with the same name as the component is. Since the component is fully cloned from the existing one, you can also copy the original TSX files under a new name and it will work straight away.
  5. Don’t forget to add all the new locations to the serialization, and check it into source control along with its codebase. Here are the locations to keep in mind:
    • /sitecore/layout/Placeholder Settings/Feature/Tailwind Components
    • /sitecore/templates/Feature/Tailwind Components
    • /sitecore/layout/Layouts/Feature/Tailwind Components
    • /sitecore/layout/Renderings/Feature/Tailwind Components
    • /sitecore/media library/Feature/Tailwind Components
    • /sitecore/templates/Branches/Feature/Tailwind Components
    • /sitecore/system/Settings/Feature/Tailwind Components

To make things simple, I recorded this entire walkthrough (and actually “talkthrough”) showing the entire process:

Hope you find this video helpful!

GitHub Action with XM Cloud

The approach I am going to show you would work for any CI/CD pipeline with XM Cloud with some level of customization, however, I will be showing it on an example of GitHub Actions.

Why?

One would ask – if my codebase is located at GitHub, why on earth would I need to leverage GitHub Actions if the XM Cloud Deploy app already provides build&deploy pipelines for GitHub? It is a valid question, so let’s answer it:

  • XM Cloud Deploy app is a black box where you have no control other than developers allow you to specify within xmcloud.build.json configuration file.
  • GitHub Actions in opposite give you much more precise control over all aspects of the process
  • It relies on the ready-use and well-tested open-source re-usable actions for you simply pick and use
  • Thanks to the above it is quick and low-code compared to other CI/CD approaches, but at the same time, it is highly customizable and may suit any enterprise-level needs (consider GitHub Enterprise in such case).
  • Actions use any OS-based runners that execute at GitHub, while the Deploy app utilizes shared XM Cloud infrastructure
  • Seamless integration into the GitHub account allows keeping all the eggs in the same basket.

With that in mind, let’s take a look at how easily we can set up multisite multi-environment XM Cloud CI/CD workflows.

Preparing XM Cloud

Let’s start with the creation of XM Cloud Project and 2 environments – Staging and Production.

Of course, you can do the above manually by using XM Cloud Deploy app, however, I automated that with a reusable PowerShell code. In order to manipulate XM Cloud from scripts, I need to obtain a pair of automation ClientID and ClientSecret first. This pair is required internally by Login.ps1 script but will be used all the way down this exercise, so save it carefully.

# Script to created a project and environments with the named provided
$projectName = "JumpStart"
$environmentStaging = "Staging"
$environmentProd = "Production"
& "$PSScriptRoot/../Security/Login.ps1"

functionCreate-Project{
    param([string]$projectName)
    $projectList = dotnet sitecore cloud project list --json | ConvertFrom-Json
    $project = $projectList | Where-Object{$_.name -eq $projectName}
    
    if(-not $project){
        Write-Warning "Project '$projectName' not found. Creating new project..."
        $output = dotnet sitecore cloud project create --name $projectName --json
        if($output -eq "Organization tier does not allow more projects"){
            return $null;
        }
        else{
            $projectList = dotnet sitecore cloud project list --json | ConvertFrom-Json
            $project = $projectList | Where-Object{$_.name -eq $projectName}
            return $project.id
        }
    }
    else{
        Write-Warning "Project $projectName already exists. Skipping create."
        return $project.id
    }
}

functionCreate-Environment{
    param(
        [string]$environmentName,
        [string]$projectId,
        [bool]$isProd = $false,
        [array]$environmentList
    )

    # Checking if environment exists.
    $environment = $environmentList | Where-Object{$_.name -eq $environmentName}
    
    if(-not $environment){
        Write-Warning "Environment '$environmentName' not found. Creating new environment..."
        $output = dotnet sitecore cloud environment create --name $environmentName --project-id$projectId --prod $isProd --json | ConvertFrom-Json
        if($output.Status -eq "Operation failed"){
            $output.Message
            return $null
        }
        else{
            return $output.id
        }
    }
    else{
        $environmentId = $environment.id
        "Environment $environmentName already exists"
        return $environmentId
    }
}
$projectId = Create-Project -projectName $projectName
$environmentList = dotnet sitecore cloud environment list --project-id$projectId --json | ConvertFrom-Json
$stagingId = Create-Environment -environmentName $environmentStaging -projectId $projectId -environmentList $environmentList
$prodId = Create-Environment -environmentName $environmentProd -projectId $projectId -isProd $true -environmentList $environmentListpo

Upon completion it will return you Environment IDs for both created environments, you can also get this information after refreshing Deploy app page:

XM Cloud Projects And Environments

Additionally, I’d like to enable SPE and Authoring and Management GraphQL API, before the deployment takes place so that I don’t have to redeploy it later:

dotnet sitecore cloud environment variable upsert -n SITECORE_SPE_ELEVATION -val Allow -id $stagingId
dotnet sitecore cloud environment variable upsert -n Sitecore_GraphQL_ExposePlayground -val true -id $stagingId
dotnet sitecore cloud environment variable upsert -n SITECORE_SPE_ELEVATION -val Allow -id $prodId
dotnet sitecore cloud environment variable upsert -n Sitecore_GraphQL_ExposePlayground -val true -id $prodId

So far so good. Let’s deploy now.

XM Cloud Provisioning

Here is the entire code of the GitHub Actions workflow I will be using for provisioning XmCloud:

name: Build & Deploy - XM Cloud Environments
on:
  workflow_dispatch:
  push:
    branches:[ JumpStart ]
    paths:
    - .github/workflows/CI-CD_XM_Cloud.yml
    - .github/workflows/deploy_xmCloud.yml
    - .github/workflows/build_DotNet.yml
    - 'xmcloud.build.json'
    - 'src/platform/**'
    - 'src/items/**'
  pull_request:
    branches:[ JumpStart ]
    paths:
    - .github/workflows/CI-CD_XM_Cloud.yml
    - .github/workflows/deploy_xmCloud.yml
    - .github/workflows/build_DotNet.yml
    - 'xmcloud.build.json'
    - 'src/platform/**'
    - 'src/items/**'
jobs:
  build-dotnet:
    uses: ./.github/workflows/build_DotNet.yml
    with:
      buildConfiguration: Release

  deploy-staging:
    uses: ./.github/workflows/deploy_xmCloud.yml
    needs: build-dotnet
    with:
      environmentName: Staging
    secrets:
      XM_CLOUD_CLIENT_ID: ${{ secrets.XM_CLOUD_CLIENT_ID }}
      XM_CLOUD_CLIENT_SECRET: ${{ secrets.XM_CLOUD_CLIENT_SECRET }}
      XM_CLOUD_ENVIRONMENT_ID: ${{ secrets.STAGING_XM_CLOUD_ENVIRONMENT_ID }}

  deploy-prod:
    if: github.ref == 'refs/heads/JumpStart'
    needs: build-dotnet
    uses: ./.github/workflows/deploy_xmCloud.yml
    with:
      environmentName: Production
  secrets:
    XM_CLOUD_CLIENT_ID: ${{ secrets.XM_CLOUD_CLIENT_ID }}
    XM_CLOUD_CLIENT_SECRET: ${{ secrets.XM_CLOUD_CLIENT_SECRET }}
    XM_CLOUD_ENVIRONMENT_ID: ${{ secrets.PRODUCTION_XM_CLOUD_ENVIRONMENT_ID }}

Please pay attention to the following parts of it:

  • on: push, pull_request and workflow_dispatch – define event to trigger. The last one means manual trigger from the GitHub UI, I will use it below.
  • branches specify to which branches push or pull request triggers apply to.
  • paths iterate the filesystem locations to be used further ahead with a runner.
  • jobs: specify what we’re going to perform, in which consequence and the dependencies between these actions.
  • each of these jobs executes a consequence of steps to take, referred from another file by uses parameter
  • needs specify the dependency from a previous action to complete successfully, prior to this one to execute.
  • if clauses define conditions for the job to run, if not met job will receive ‘Skipped’ status along with all the other dependant jobs.
  • secrets are taken from stored GitHub Actions secrets and passed down to the jobs

Secrets

For each of the jobs I need to provide 3 parameters from the secrets:

  • XM_CLOUD_CLIENT_ID and XM_CLOUD_CLIENT_SECRET – is a pair of automation ClientID and ClientSecret, the same we obtained at the beginning of this article.
  • STAGING_XM_CLOUD_ENVIRONMENT_ID or PRODUCTION_XM_CLOUD_ENVIRONMENT_ID are the IDs we obtained upon environment creation. You may always look them up in the Deploy app.

So, we have 3 jobs created from 2 steps of consequences of action:

  • Build the DotNet solution
  • Deploy the solution and items to an XM Cloud instance

Build the DotNet solution workflow:

name: Build the DotNet Solution

on:
  workflow_call:
    inputs:
      buildConfiguration:
        required:true
        type: string

jobs:
  build-dotnet:
    name: Build the .NET Solution
      runs-on: windows-latest
      steps:
      - uses: actions/checkout@v3
      - name: Setup MSBuild path
        uses: microsoft/setup-msbuild@v1.1
      - name: Setup NuGet
        uses: NuGet/setup-nuget@v1.0.6
      - name: Restore NuGet packages
        run: nuget restore JumpStart.sln
      - name: Build
        run: msbuild JumpStart.sln /p:Configuration=${{ inputs.buildConfiguration }}

The top part of the file within on section receives the parameters from a calling workflow. Within jobs, we specify steps to take. Important clause – uses – executes an action from the repository of published actions.

The codebase is open so you may take a lookup for a better understanding of what it is doing and how parameters are being used, for example, we’re passing buildConfiguration parameter down to the action in order to define if we need to debug or release.

Now let’s take a look at a more advanced workflow Deploy the solution and items to an XM Cloud instance:

name: Deploy the solution and items to an XM Cloud instance

on:
  workflow_call:
    inputs:
      environmentName:
        required:true
        type: string
    secrets:
      XM_CLOUD_CLIENT_ID:
        required:true
      XM_CLOUD_CLIENT_SECRET:
        required:true
      XM_CLOUD_ENVIRONMENT_ID:
        required:true

jobs:

  deploy:
    name: Deploy the XM Cloud ${{ inputs.environmentName }} Site
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-dotnet@v2
      with:
        dotnet-version:'6.0.x'
    - run: dotnet tool restore
    - run: dotnet sitecore --help
    - name: Authenticate CLI with XM Cloud
      run: dotnet sitecore cloud login --client-credentials --client-id ${{ secrets.XM_CLOUD_CLIENT_ID }} --client-secret ${{ secrets.XM_CLOUD_CLIENT_SECRET }} --allow-write
    - name: Deploy the CM assets to XM Cloud
      run: |
        result=$(dotnet sitecore cloud deployment create --environment-id ${{ secrets.XM_CLOUD_ENVIRONMENT_ID }} --upload --json)
        echo $result
        isTimedOut=$(echo $result | jq ' .IsTimedOut')
        isCompleted=$(echo $result | jq ' .IsCompleted')
        if [ $isTimedOut = true]
        then
            echo "Operation Timed Out."
            exit -1
        fi
        if ! [ $isCompleted = true]
        then
            echo "Operation Failed."
            exit -1
        fi
        echo "Deployment Completed"

Please pay attention to actions/setup-dotnet@v2 – it relies on this codebase and you pass the desired dotnet version as the parameter to it: with: dotnet-version: '6.0.x'.

You can also execute commands within the context of an isolated VM where the steps execute, by using run clause, such as run: dotnet tool restore.

What is notable here is that Sitecore CLI is written with .NET Core which means it is truly cross-platform and may run on Mac and Linux. Therefore we may employ better lightweight runtimes for it with runs-on: ubuntu-latest clause, instead of using Windows-based runtime.

We may pass secrets right into the executed command and capture the execution results into a variable to process:

result=$(dotnet sitecore cloud deployment create --environment-id ${{ secrets.XM_CLOUD_ENVIRONMENT_ID }} --upload --json)

Note, that we actually must do the above in order to receive the outcomes of the above command rather than a binary flag showing if it was executed or not. If the CLI commands execute in principle, it returns positive status code 0, while we need to process the output and throw status codes based on it.

TIP: Actions and workflows related to a specific git branch they belong to. However, they won’t be seen in GitHub Action until you bring them to main branch. Once they reach main, they become seen from the UI and you can trigger and manually execute the workflows specifying any desired branch.

I already took care of the above so now can execute, this time manually:

Run Workflow

.. and the result:

Provision Xmc Workflow

After the execution completes we can optionally test the environments if they are up and running. They are good, and feature in my case three websites per each of the environments. These websites were provisioned from the serialization I’ve previously done, however, they only exist in these CM environments and have not yet been published.

Sites To Publish

You can do that by clicking Publish all sites button, however, I prefer using the command line:

# need to connect to the environment first
    dotnet sitecore cloud environment connect --environment-id STGqNKHBXMEENSWZIVEbQ
    dotnet sitecore publish --pt Edge -n Staging
    dotnet sitecore cloud environment connect --environment-id PRDukrgzukQPp0CVOOKFhM
    dotnet sitecore publish --pt Edge -n Production

After publishing is complete, we can optionally verify it using GrpahQL IDE and generate an Edge token to be used as the Sitecore API Key. Both could be done by running New-EdgeToken.ps1 script which will generate and output a token and then launch GraphQL IDE to test it.

Configuring Vercel

For the sake of an experiment, I am using my personal “hobby”-tier Vercel account. Of course, you don’t have to use Vercel and can consider other options, such as Netlify, Azure Static Web Apps, or AWS Amplify. I am going to talk about configuring those in later posts, but today will focus on Vercel.

Let’s navigate to Account Settings. There we need to obtain two parameters:

  • Vercel ID from the General tab
  • A token that allows external apps to control Vercel account, under the Tokens tabAccount Settings Tokens

I am going to create two projects in it, named staging-jumpstart and production-jumpstart which will deploy under staging-jumpstart.vercel.app and production-jumpstart.vercel.app hostnames correspondingly. To do so firstly I need to provide a relevant source code repository, in my case that would be obviously GitHub. Other than that it requires choosing the implemented framework (Next.js) and providing a path to the source folder of Next.js app, which it nicely auto-recognized and highlights with Next.js icon. Finally, we need to provide at least three environmental variables:

  • JSS_APP_NAME – in my case it is jumpstart.
  • GRAPH_QL_ENDPOINT which is a known value https://edge.sitecorecloud.io/api/graphql/v1
  • SITECORE_API_KEY which we obtained at a previous step from running New-EdgeToken.ps1 script.

Vercel Setup Project

Clicking Deploy after submitting the above will deploy the website and it will be already accessible by the hostname, correctly pulling the layout data from Experience Edge because:

  • we already published all the sites for each environment to Experience Edge, so it is available from there
  • we instructed the site on how to pull the data from Edge with a combination of JSS_APP_NAME, GRAPH_QL_ENDPOINT, and SITECORE_API_KEY.

Vercel Projects

At this stage we can celebrate yet another milestone and will grab a Project ID parameter from each of these deployed sites – staging-jumpstart and production-jumpstart in my case:

Result Production Deploy Vercel After Creation And Passing Tokens

Build and Deploy Next.js app

Finally, we got enough to configure another workflow for building and deploying Next.js application. The syntax is the same as we did for XM Cloud workflow.

We need to provide a workflow the following parameters, and we have them all:

  • VERCEL_ORG_ID to specify which Vercel account it applies to
  • VERCEL_TOKEN so that it becomes able to control a given Vercel account
  • VERCEL_JUMPSTART_STAGING_ID and VERCEL_JUMPSTART_PRODUCTION_ID – a project ID to deploy

Homework: you can go ahead and parametrize this script for even better re-usability passing site name as a parameter, from a caller workflow.

name: Build & Deploy - JumpStart Site

on:
  workflow_dispatch:
  push:
    branches:[ JumpStart ]
    paths:
      - .github/workflows/CI-CD_JumpStart.yml
      - .github/workflows/build_NextJs.yml
      - .github/workflows/deploy_vercel.yml
      - 'src/jumpstart/**'
  pull_request:
    branches:[ JumpStart ]
    paths:
      - .github/workflows/CI-CD_JumpStart.yml
      - .github/workflows/build_NextJs.yml
      - .github/workflows/deploy_vercel.yml
      - 'src/jumpstart/**'

jobs:
  build-jumpstart-site:
  # if: github.ref != 'refs/heads/JumpStart'
  uses: ./.github/workflows/build_NextJs.yml
  with:
    workingDirectory: ./src/jumpstart

  deploy-jumpstart-staging:
    uses: ./.github/workflows/deploy_vercel.yml
    needs: build-jumpstart-site
    if: always() &&
      github.repository_owner == 'PCPerficient' && needs.build-jumpstart-site.result != 'failure' && needs.build-jumpstart-site.result != 'cancelled'
    secrets:
      VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
      VERCEL_PROJECT_ID: ${{ secrets.VERCEL_JUMPSTART_STAGING_ID }}

  deploy-jumpstart-production:
    uses: ./.github/workflows/deploy_vercel.yml
    needs: build-jumpstart-site
    if: always() &&
      github.repository_owner == 'PCPerficient' && needs.build-jumpstart-site.result != 'failure' && needs.build-jumpstart-site.result != 'cancelled'
    secrets:
      VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
      VERCEL_PROJECT_ID: ${{ secrets.VERCEL_JUMPSTART_PRODUCTION_ID }}

There are three jobs here, with the last two running in parallel:

  • build-jumpstart-site
  • deploy-jumpstart-staging
  • deploy-jumpstart-production

Build job:

name: Build a Next.js Application

on:
  workflow_call:
    inputs:
      workingDirectory:
        required:true
      type: string

jobs:
  build:
    name: Build the NextJs Application
    runs-on: ubuntu-latest
    env:
      FETCH_WITH: GraphQL
      GRAPH_QL_ENDPOINT:https://www.google.com
      DISABLE_SSG_FETCH:true
    defaults:
      run:
        working-directory: ${{ inputs.workingDirectory }}
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version:18.12.1
      - run: npm install
      - run: npm run build
      - run: npm run lint

Deploy job:

name: Deploy asset to Vercel

on:
  workflow_call:
    secrets:
      VERCEL_TOKEN:
        required:true
      VERCEL_ORG_ID:
        required:true
      VERCEL_PROJECT_ID:
        required:true

jobs:
  deploy:
    name: Deploy the rendering host to Vercel
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: lts/*
      - uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-args: ${{ fromJSON('["--prod", ""]')[github.ref != 'refs/heads/JumpStart']}}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID}}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}}
          scope: ${{ secrets.VERCEL_ORG_ID}}
          working-directory: ./

At this stage, you’ve got and learned everything enough to implement the above approach for your own XM Cloud solution.

Visual Code Extension

The good news is that GitHub Action has an extension for VS Code which allows to manage workflows and runs:

  • Manage your workflows and runs without leaving your editor.
  • Keep track of your CI builds and deployments.
  • Investigate failures and view logs.
  1. Install the extension from the Marketplace.
  2. Sign in with your GitHub account and when prompted allow GitHub Actions access to your GitHub account.
  3. Open a GitHub repository.
  4. You will be able to utilize the syntax features in Workflow files, and you can find the GitHub Actions icon on the left navigation to manage your Workflows.

Hope you enjoyed the simplicity of GitHub Actions and will consider implementing your solutions with it!

XM Cloud: a modern way to content management and import with Authoring GraphQL API

When it comes to content management, how would you deal with automating it?

You’ve probably thought of Data Exchange Framework for setting up content import from external sources on a regular basis, or Sitecore PowerShell Extensions as the universal Swiss Army knife that allows doing everything.

Sitecore XM Cloud is a modern SaaS solution and therefore offers you one more way of managing and importing content via GraphQL mutations. This is also an option for the latest 10.3 XM/XP platforms bringing them a step closer to the composable world of today.

There is a video walkthrough of the wholee exercise at the bottom of this post.

Please welcome: Authoring and Management API!

The documentation prompts a broad and awe-inspiring list of things you can do with Authoring API against your instance: create and delete items, templates, and media. It also empowers you to do some operations around site context and perform a content search on your CM instance.

Management API in addition gives you control over operations using queries and mutations for the following GraphQL types:

  • Archiving
  • Database
  • Indexing
  • Job
  • Language
  • Publishing
  • Security
  • Workflow
  • Rules

With that in mind, you can create and structure your content, reindex, and publish it to Experience Edge entirely using this API. So let’s take a look at how it works!

Uploading a picture to Media Library using GraphQL Authoring API

First of all, it is disabled by default, so we need to switch by setting Sitecore_GraphQL_ExposePlayground environmental variable to true. Since these variables expand at build time you also need to re-deploy the environments

Enable Api

Once deployment is complete, you can start playing with it. Security in a composable world typically works with OAuth, Authoring and Management API is not an exclusion here. In order to obtain an access token, you need to authorize it first with your client ID and client secret which you set up with XM Cloud Deploy app:

Token

There are different ways of authorization (for example, using CLI dotnet sitecore cloud login command), but since the need to fully automate the routine, I will be using /oauth/token endpoint. Also, it is worth mentioning that after getting initially already authorized with CLI, your client ID / secret pair is stored at .sitecore\user.json file so let’s take it from there. Here’s the code:

$userJson = "$PSScriptRoot/../../.sitecore/user.json"

if(-not(Test-Path$userJson)){
    Write-Error"The specified file '$userJson' does not exist."
    return
}

$userJson = Get-Content$userJson | ConvertFrom-Json
$clientId = $userJson.endpoints.xmCloud.clientId
$clientSecret = $userJson.endpoints.xmCloud.clientSecret
$authorityUrl = $userJson.endpoints.xmCloud.authority
$audience = $userJson.endpoints.xmCloud.audience
$grantType = "client_credentials"

$body = @{
    client_id = $clientId
    client_secret = $clientSecret
    audience = $audience
    grant_type = $grantType
}

$response = Invoke-RestMethod -Uri "${authorityUrl}oauth/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body
return $response.access_token

Now we got the access token and it should be passed as a header with every single request to GraphQL API:

“Authorization” = “Bearer <access_token>”

Next, let’s make a mutation query that returns us a pre-signed upload URL from the passing API endpoint and a target Sitecore path that you want to upload your media to. Here’s the code:

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true, HelpMessage="The URL of the endpoint where the file will be uploaded.")]
    [string]$EndpointUrl,
    [Parameter(Mandatory=$true, HelpMessage="The JWT token to use for authentication.")]
    [string]$JWT,
    [Parameter(Mandatory=$true, HelpMessage="The path of the file to be uploaded.")]
    [string]$UploadPath
)

$query = @"
mutation
{
  uploadMedia(input: { itemPath: "$UploadPath" }) {
    presignedUploadUrl
  }
}
"@

$body = @{ query = $query} | ConvertTo-Json
$headers = @{
    "Content-Type" = "application/json"
    "Authorization" = "Bearer $JWT"
}

# Invoke the GraphQL endpoint using Invoke-RestMethod and pass in the query and headers
$response = Invoke-RestMethod -Method POST -Uri $EndpointUrl -Headers $headers -Body $body
$result = $response.data.uploadMedia
return $result.presignedUploadUrl

Now that we have the pre-signed upload URL, we can perform media upload passing the local file to process:

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true, HelpMessage="The URL to upload the file to.")]
    [string]$UploadUrl,
    [Parameter(Mandatory=$true, HelpMessage="The JWT token to use for authentication.")]
    [string]$JWT,
    [Parameter(Mandatory=$true, HelpMessage="The path to the file to be uploaded.")]
    [string]$FilePath
)

if(-not(Test-Path$FilePath)){
    Write-Error"The specified file '$FilePath' does not exist."
    return
}

$result = & curl.exe --request POST $UploadUrl --header "Authorization: Bearer $JWT" --form =@"$FilePath" -s
$result = $result | ConvertFrom-Json
return $result

This script will return the details of a newly uploaded media item, such as:

  • item name
  • item full path
  • item ID

I combined all the above cmdlets into a single Demo-UploadPicture.ps1 script that cares about passing all the parameters and performs the upload operation:

Powershell

The upload immediately results in Media Library at the requested path:

Result

Pros:

  • a modern platform agnostic approach
  • works nicely with webhooks
  • allows automating pretty much everything
  • excellent management options making DevOps easier

Cons:

  • cumbersome token operations
  • doesn’t allow batching, therefore takes a request per each operation

Verdict

It is great to have a variety of different tools in your belt rather than having a single hammer in a hand with everything around turning into nails. Hope this new tool brings your automation skills to a new level!

The ultimate guide to Sitecore XM Cloud

If you ask anyone in the Sitecore community what was the biggest hype of 2022 – there wouldn’t be any other opinion than mentioning XM Cloud. Let’s take a look at this latest and shiny SaaS offering!

Content

Overview, Architecture, Features

Introduction

Sitecore XM Cloud is a cloud-based platform that provides tools and features for managing and optimizing digital customer experiences. The platform is a headless SaaS solution initially designed to resolve certain points of pain XP platform previously suffered from, including

  • Platform upgrades: one of the time-consuming and uncertain activities that eat out a significant budget, but on their own bring very little of new features. I have written a decent series of blog posts exclusively devoted to the Sitecore platform upgrades and the accompanying complexities. With the SaaS model, you get upgrades automatically done for you by Sitecore.
  • Infrastructure management and maintenance, security maintenance, taking care of scalability: all that was previously done by the end clients or partners now is fully managed by Sitecore itself.
  • A bottleneck of Sitecore developers: lack of availability of developers with specific knowledge of Sitecore internals may encounter a staffing bottleneck when building and maintaining Sitecore-based applications, including the complexity of the platform, limited documentation, dependence on the Sitecore support team, and limited resources. Since most of that is now provided by the vendor, development shifts mainly to the “head” and requires generic frontend skills that are much easier to come by.
  • Platform architectural bottleneck: in addition to all the above, previously scaling up your CD servers in a non-headless environment was quite a bottleneck and came at a cost compared to XM Cloud which does not have CDs at all, but only serves content via highly available Edge APIs endpoints.

That results in a drastically improved development process, velocity, and reliability; not to mention saved budgets. Clients can mostly focus on building the sites for visitors, where the platform itself gets care from the vendor.

Licensing

Before talking about licensing, it is important to understand how this SaaS offering is structured.

XM Cloud hosting platform includes the concepts of organizations, projects, and environments. An organization is the equivalent of an XM Cloud subscription. To initially access the XM Cloud portal, you must be a member of an “Organization” admin account at Sitecore. Once you have access to this account, you can manage your users, give them the required access or even set them as admins, create projects, set up environments for those projects, and promote your code through these environments. In old XP terminology, each environment is a Sitecore instance.

There are three environments per project: one production and two non-prod – typically for development, staging, testing, or other purposes. With the XM Cloud portal, you can easily manage and access all of your Sitecore instances in the cloud.

Coming back to the licensing model, it is similar to a subscription license from Sitecore platforms and its cost model was simplified very much to be based on traffic and consumption.

Each XM Cloud license includes a certain number of projects and environments, and you can use these to manage your different Sitecore instances. Depending on your specific requirements, you may be able to use different projects to handle different instances. You can use environments to set up and configure your development, staging, testing, and production environments, as well as any other environments you may need. If you have any questions about how to best use projects and environments, or if you have any other licensing questions, it is recommended that you speak with your Account Manager, who will be able to provide more information and guidance to meet your specific needs.

Interestingly, in order to run local docker containers with XM Cloud, one requires to have a valid Sitecore license. I cannot say if your existing partner’s license file will work with local containers development, as being a Sitecore MVP I was given a universal Sitecore license file which worked well. For building and deploying a source code with a built-in Deploy App you don’t need to provide a license file – it is assumed from your organization (subscription).

xm cloud license

Architecture

In very simplified terms, the architecture could be explained by the below diagram provided by Sitecore:

XM Cloud architecture

Not all the internals of the architecture are mentioned above (ie. ACR, Kubernetes, etc. are missed out), but should you really care about anything within the dashed area? All that is Sitecore-managed and developers typically focus on the development of the front-end website (also known as a “head”) which is most often built with Next.js. Of course, other frameworks also would work with XM Cloud, however, there’s lots of plumbing to be done that Next provides out-of-box.

One of the most important features of XM Cloud is Webhook Framework. It is built into XM Cloud in the same as is for 10.3 platforms. In a composable world of decoupled SaaS products webhooks are used to notify external services about changes in XM Cloud, for example, to validate and even cancel Workflow state transitions.

For example, in the old good XP platform, we used events to notify that publishing to CD has been completed. One of the possible scenarios was using Core database to pass the remote events as that was an architectural feature of a monolith platform. In a composable world that cannot be the case, as systems cannot share resources in that way and can only communicate through APIs. You don’t have CDs and publish to Edge which reasonably also has its own webhooks. You could also utilize webhooks on git repository or at Vercel side for example.

With some obvious architectural limitations, it is possible to customize XM Cloud in a similar way as we did it with XP by applying patches, but there is an expectation is that developers would customize less and less with time and platform growth. From the functionality point of view, these customizations would focus on data and synchronization rather than patching system features.

Speaking about the drawbacks of XM Cloud I could state single region geolocation, which you must specify initially.

Cloud Portal

It is a visual dashboard of your Organization. Here you can get all your tools in the same place, based on what you have in your subscription. In addition – there are shortcuts to Documentation and you can access Support from the Portal as well.

cloud portal

You can create projects and environments right from this portal. Choose between a starter template or setting up your GitHub repository and if the latter has been chosen – once you grant access to XM Cloud Deploy App to your account and choose the desired repository – it will perform the deployment in the background.

You choose a specific git branch for the desired environment (for example – main branch deploys to the production environment, while develop branch – is to testing) and can also enable auto-deploy upon each commit to a chosen branch. There is nothing else to set up or configure on top of the above, like CI/CD pipelines – Deploy App already knows what to do.

Is GitHub the only way to provide source code for build and deployment? The answer is both yes and no: from GUI it indeed currently only supports GitHub, with later plans to add support for other popular version control hosting providers, such as Azure DevOps, GitLab, Bitbucket, etc. But from CLI one can have more configuring options.

And not just that – almost everything you can do on the portal with GUI you can do remotely with CLI, for example. However, many of the Cloud Portal tools are not available for local development: Pages, Components, Explorer, Deploy App, and Dashboard itself all run exclusively in the cloud and are not available to run locally.

So, how would I develop it at my local rendering host and test it then with Pages or Experience Editor running in the cloud? Currently, there’s a workaround to configure tunneling for the local Rendering Host with a reverse proxy like ngrok or localtunnel, so that your local rendering host server becomes available outside.

If using default GitHub is not an option or you want to customize the automation and/or set your own CI/CD pipeline, there’s another feature of Cloud Portal named Authentication Clients available – an access token generator for XM Cloud.

This is how the documentation describes it:

“When you generate an authentication client, the client creates credentials that include a client ID and a client secret. You can use the credentials to request a JSON Web Token for your CM instance or to request a JWT for Experience Edge XM.”

So, that is an effective tool for creating tokens for approving custom tools for the automation and/or setting your own CI/CD pipeline.

The third tab named Status provides a basic but helpful overview of a deployment process.

I would recommend reading through the official Cloud Portal documentation.

Identity

With the XP platform, we used to have an Identity Server however the one is no longer useful in a genuine composable world. Sitecore had to re-think the authentication approach and implemented a new Unified Identity system so that it offers SSO across all applications of the Composable DXP family.

Integrations

How XM Cloud integrates with Content Hub DAM and CMP out-of-box and reference to the CMP/DAM connector now in the base image.

Sitecore Experience Edge

Experience Edge for XM comes out of the box with XM Cloud and is the default destination for publishing content. It is a content delivery service that provides scalable, globally replicated access to Sitecore Experience Platform items, layout, and media through an API. It uses CDN networks to distribute published content across the globe, ensuring a fast experience at scale.

The Edge for XM connector is required to publish the content from your pages and components in Sitecore XM Cloud to the highly scalable Sitecore Experience Edge delivery platform. It is also included.

GraphQL schema used by Experience Edge in XM Cloud is the same as used for 10.3 platforms (with a minor difference in temporal query complexity limits). However, XM Cloud schema is different from those used in Content Hub and Content Hub One as they implement different underlying data structures.

Speaking about security, previously on XP when we dealt with “protected” pages, security data went along with published content to CD servers, where Sitecore platform implemented it in the correct way. With XM Cloud we don’t have a CD server any longer, we publish to Edge Content gets published to Edge regardless of that permissions. Of course, it is not immediately available to the outer world – without a valid API key, it is not possible to access it. But instead of a fully compliant Sitecore CD server we now got a “head” which is a totally detached piece of technology. Having API Key, head may, or may not respect the security rules – Edge will give it out anyway. So, you should extra care and test these things while developing head application on your rendering host.

Familiar XP tools that have gone out of XM Cloud

Since we do not have xDB and xConnect within XM Cloud architecture, many features from XP did not find their way to the new composable world. We have to say goodbye to:

  • XDB
  • EXM
  • Identity Server
  • FXM
  • Path Analyzer

In addition, because of the exclusively headless architecture of XM Cloud, not having CD servers, and operating through Edge these features also went off this SaaS platform:

  • MVC renderings, including classical SXA
  • Publishing Service
  • Sitecore Forms
  • Custom Search Indexes
  • Universal Tracker

Sitecore Forms have gone simply because there is nowhere to submit the data back to. It is expected that Sitecore comes up with a sort of SaaS forms component to be used with a composable family of products. Meanwhile, you can consider using something like Jotforms, Marketo, Hubspot Forms, etc.

As for implementing search functionality – despite Solr still being a part of this platform for supporting XM needs (same as does it for XP), however, without having CD servers there is no more reason to have custom search indexes. Implementing a search feature for your website now requires a composable search component. I will talk through it in more detail below.

Sitecore platform features that still remain

  • Media Library exists and functions exactly the same as with XP
  • The above could be said about Workbox and workflows
  • Content Editor and Experience Editor remain untouched
  • We still have the luxury of poking the system internals with Sitecore PowerShell Extensions, this component is built-in since that is a requirement for the same built-in SXA Headless
  • Many other less important features stay

As said above, custom search indexes are no longer available and one has to select from a choice of composable options: Sitecore Search, Coveo, or Algolia – those first come into my mind. All these products are platform agnostic and will integrate seamlessly with any site regardless of underlying CMS or web engine.

There might be several approaches for implementing external search with XM Cloud:

  • Using search-based queries from Experience Edge directly. The development team can write searches against our Experience Edge endpoints and call from their application.
  • Using a crawling search engine. Sitecore Search has been released and got already proven on several implementations including sitecore.com website. Currently, it indexes against rendered HTML markup, however, Edge support should come up with future releases.
  • There’s still an option of setting up and configuring your own external Solr solution to send content to it. Also, SearchStax Studio for XM Cloud could be another alternative option. 

Headless SXA

The headless part of SXA is provided with Sitecore XM Cloud, a cloud-based CMS platform. It allows developers to build and deploy headless websites using XM Cloud, taking advantage of all the known existing SXA features, such as Page Layout, Partial Layout and especially Rendering Variants.

In addition, new concepts were introduced: Page Branches, and standard values for layout on a per-website basis.

What I found really great was the out-of-box implementation of SEO-related things, such as:

  • sitemaps
  • robots.txt
  • redirect items
  • redirect maps
  • errors (404 and 500)

This version of SXA supports Next.js as a first-class citizen and therefore has a revised list of components, leaving only those most basic to work (here they are all). With years of SXA practices, it became a default way of doing things to clone components and add rendering variants to existing ones. Assuming the same, Rendering Variants remain with us in a headless world, however, Scriban is no longer there, replaced with Next.js. Here is an example of how Rendering Variants are defined within a single promo.tsx file with Next.js implementation – there are two of them named Default and WithText. It’s really simple and similar to Scriban.

When you set up an empty site you have default SXA Headless components (at the components tab). Drag and drop works, and also works dragging components along the screen to the position. The right-hand-side section features component properties, including styling.

component tab

There is a content tab that shows item properties similar to what it was before in Content Editor.

CLI and Serialization

Similar to the XP platform, CLI operates through built-in Sitecore Management Services in order to support Sitecore CLI.

Almost everything that can be done through the Cloud portal can be also done through the Sitecore CLI. The latest version of CLI allows the creation of Projects, New Environments and Deployments, and much more.

Sitecore CLI allows you to take existing serialized content and move it directly into XM Cloud. Since serialization is done in YAML format, it stays the same as with XP and also is compatible with Unicorn, therefore correctly formatted content is easily portable to XM Cloud. I want to emphasize that it is only about serialized content, Unicorn itself is an XP module and cannot be installed on XM Coud.


New Tools and Interfaces

Old good Content Editor and Experience Editor still remain with us and that’s good news for those who habitually stick to the interfaces. they used to. Nevertheless, this blog post will familiarize you with the totally new apps and interfaces coming with XM Cloud.

Sites

This application opens by default. It shows up all of the websites available in the system with the predictive typing search bar to filter them out. Clicking any of the sites opens them up in a Pages app, as expected.

What is more interesting is the ability to create websites directly from this interface by clicking the “Create website” button.

create website

Pages

If you played around Horizon in the latest versions of XP such as 10.2, you will immediately recognize this user interface. Pages are built on the foundation of Horizon:

Pages application

The Pages application has 5 views which you select from the left navigation stripe:

  • Pages
  • Components
  • Content
  • Personalize
  • Analyze

I personally find it a little confusing, that some views have the same names and apps. For example, by clicking the Components view of the above Pages app from the left bar navigation, you will see a component selector which you can drag and drop to the editable page. However, this is totally different from the Components application which is a turbocharged tool for creating components to be used on a page, which you later choose by the mentioned component selector of Pages application.

I want to highlight that Pages was done with attention to minor details. It has very pleasant animation effects for many transitions, leaving you with a premium experience feelings. There is no “Save” button anywhere as Pages application autosaves your changes.

It also has support for multiple websites selected from a dropdown. Preview, Devices Experience, Navigation Tree – all these features are located at the same interface you would expect in order to have a productive editing experience.

Components

The tool is still in its early stages, but it has a lot of potentials. Its purpose is to enable content creators and marketers to create components without the need for a developer to start from scratch.

There are lots of styling options to specify custom types, and custom fonts, at least it looks advanced.

Here we create the component and publish it, so that component becomes available in the component library of the Pages app. The intention is for us to be able to put components together and create them together from basic page elements, images, links, and so forth. Starting with a grid, then you simply start adding columns, set alignment, and apply other UI-related things.

But what is the most impressive feature of Components app is – we not only create and customize individual components similar to the way we do it in SXA, but now we create entire components completely from scratch, we can style them and can hydrate them with data from an external data source. By saying external I mean really external: one of many 3rd party content providers and headless CMSs, like Content Hub One, Contentful, Kontent.ai, etc., and all that with zero code!

All you need to do is to provide an API endpoint and use a mouse to select what fields from the source you want to map. For those who are behind a tough firewall or strict corporate policies, there is an option to directly paste in a sample JSON snippet to map against it. They don’t ever have to hit the live API when creating a datasource.

What is even more mind-breaking is that you can feed the same single component from various external systems at the same time. Image a Promo Block component that you can easily drag and drop to a page that consists of:

  • Heading title that sits natively at Sitecore XM Cloud
  • Image taken from Content Hub DAM
  • Localized promo content taken from Contentful CMS
  • Call to action button that is wired up to CDP

In my opinion, that is a superpower of XM Cloud editing capabilities! I very much recommend watching this short video demonstrating the whole power of the new Components app.

Components

How the data is updated and invalidated with Components? It seems that the data is pulled in at the moment when the connection is established, and not get refreshed at the moment of publication.

Personalize

A cut version of Sitecore Personalize comes built into Pages interface of XM Cloud to allow for basic view event personalization. Therefore there isn’t a one-to-one match of old XP rulesets to the new ones, so a deeper analysis and re-evaluation of your personalization strategy is needed.

With XM Cloud there’s a new way of doing personalization coming out of the box: we have a limited set of rules available to us and those are primarily rules based on the current session, such as day of the week, a visitor’s country, to an operating system of visitor’s current device. However, there’s no way you can manage the built-in Personalize tenant directly from the XM Cloud interface.

Checking carefully, you can still find some rules based on historical data: an example of a history-based rule is the number of page views a visitor has visited pages within the past (max) 30 days. Of course, once you enable full Sitecore Personalize a set of tools will expand the functionality to a wider range of options.

Unlike XP where we would personalize each component individually, in XM cloud we create page variants to achieve that so that we can define the audience that will be exposed to that page variant.

Configuring the audience, you see the default set of rules, I counted 14:

  • Point of sale: The visit is to point(s) of sale
  • Region: The visitor is in region(s) during the current visit
  • Country: The visitor is in country(s) during the current visit
  • Visit day of the month: The visit is on a day of the month that compares to number based on your organization’s time zone
  • Day of the week: The visit is on a day(s) of the week, based on your organization’s time zone
  • Month of visit: The visit is in month(s), based on your organization’s time zone
  • First referrer: The visitor comes from a URL that compares to referrer in the current visit
  • UTM value: The visit includes a UTM type that compares to UTM value
  • Operating system: The visitor is using operating system(s) during the current visit
  • Device: The visitor is using a device type(s) device during the current visit
  • Number of page views: The visitor has visited page within the past x days and the total number of page views compares to a number of views
  • First page: The visit has started on a page that compares to page during the current visit
  • Page view: The visitor has visited page name(s) during the current visit
  • New or returning visitor: The visitor is a specific type to your site

After specifying the audience, you then process customizing components for that page variant.

That is implemented in a very similar manner as it was back in the XP: specify the data source item with the desired content, choose a different component to replace the default one, or simply choose to hide the component as another option.

Personalize

The bigger question comes about how personalization works with Edge, without having CD servers previously responsible for executing personalization. It is important to understand that the personalization logic is now happening at the Vercel/Next.js middleware level.

A middleware package intercept browser request at Vercel in order to do audience matching with Personalize, and then serves the relevant content from the Edge, for example by substituting personalized Edge content from one to another. The above approach does not imply any performance penalties at all since all the parts of this chain are super-fast at CDN/Jamstack level at Vercel and also because the content is already cached at Experience Edge. I would recommend spending some time reading through more details about the built-in personalization in XM Cloud by this link.

Analyze

Another excellent embedded tool powered by Boxever page-level analytics: pages built-in with Pages have embedded tracking tag therefore analytics become auto-available. Just out of the box it will empower you to see:

  • the browsers the operating system
  • the source where they came from
  • the pages: first page, entry page, top pages
  • visited top countries
  • by source visits
  • by time of the day

Analytics presents data with a heat map for the time of the day with most traffic shown by darker rectangles.

analyze

Similar to the previous example of Personalize, this analytics system is now built on top of the Sitecore CDP. Previously all the analytics was taken out from Sitecore XDB, and without having that in SaaS cloud offerings the only option for providing built-in analytics and personalization was to rely on CDP and Personalize in some way.

Explorer

In addition to Pages you can use Explorer for editing content. I think the main idea of this interface was to give editors the ability to switch rapidly between visual and filled editing interfaces.

Explorer presents you the content with some similarity to Horizon performing on XP: kind of a Content Editor with navigation and the ability to publish and edit items at the fields level. You can do many expected content operations here like modify, copy, upload download media assets, etc.

Interestingly that Sitecore decided to walk away from a tree-based style of presenting content structure to a drill-into way of navigation. It definitely makes things clear, and when we need to take a look at the entire structure of the content tree – there is still old good Content Editor to our help.

explorer

Site Identifier

This is an interface to add a tracker to your websites if they are missing so that they become trackable with Analyze section of Pages app.

site identifier

That was an overview of the most important new applications and interfaces coming with XM Cloud. The part of this post will take you through the development experience with XM Cloud.


Development with XM Cloud

In the final post of the XM Cloud series, we’ll talk about the development process with XM Cloud and its nuances. I assume you’re either familiar with the traditional way of Sitecore development or at least get some basic understanding of the process.

In order to identify the difference in the development, let’s quickly recap architectural features of XM Cloud that affect:

  • there are no more Content Delivery (CD) servers available
  • therefore content gets published to Sitecore Edge
  • that, in turn, enforces headless development only

Sitecore Next.js SDK

That means we now have a “head” web application running on a Rendering Host. That would be most likely built with Sitecore Next.js SDK – using Next.js on top of React with TypeScript running on a Node-powered server. Next.js is a framework created by Vercel that is built on top of the React library, designed to help developers build production-ready applications with little need for configuration and boilerplate code.

Of course, as a natively headless platform Sitecore allows using any other framework or technology, like Vue, Angular, or .NET Renderings, however, if React is your choice then Next.js SDK will offer you lots of features available out of the box, including

  • Powerful routing mechanism
  • Layout Service fetching and mapping
  • Placeholder Resolver
  • Multi-language support
  • Field Helper components
  • Components Factory
  • Experience Editor support
  • Sitecore Analytics support

I would recommend going deeply through the relevant documentation, as it covers it all in the detail.

A headless architecture consists of a back end with a layer of services and APIs and a front-end/client/user-facing application. The front-end application, or presentation layer, retrieves data from the CMS using API endpoints and uses that data to populate or hydrate the markup it generates.

Without having CD servers any longer, there is no place to execute HTTP request pipeline extensions. Experience Edge provides just raw published data in a headless way, there is no place to apply any logic. But what if you need to modify a request, for example?

In that case, all that logic gets moved to a “head” application, since changes will need to be made in the hosting environment for the client application.

If the Next.js application is hosted on Vercel, a Vercel Serverless Function can be set up to process incoming HTTP requests and generate a response. For Cloudflare Pages, one can choose Cloudflare Worker for the same purpose, and so on.

Similarly, any other integrations or personalization previously taken place on a CD server should be refactored to work with the “head” application at the Rendering Host. It is recommended to use out-of-process API-based integration as much as possible to maintain loose coupling.

Development Modes

There are two approaches for developers to interact with XM Cloud and implement customer requirements: Edge Mode and Fully Local.

With Edge Mode, you developed a classical JavaScript-based rendering app on a local node server.  This app connects to which will connect to the GraphQL endpoint of the Experience Edge which XM Cloud publishes to. This option does not require a license, as Edge relates to your Cloud Subscription which is aware of your license. Therefore you can scale and outsource the development as much as you desire. You can also develop on Mac or Linux, as soon as the OS can run a Node server.

Fully Local development requires containers running on a Windows-based environment and you need to have a valid license file to run it. If running on a client version of Windows (say, 10 or 11) make sure to reference 1809-based images in your .env file.

SOLUTION_BASE_IMAGE=mcr.microsoft.com/windows/nanoserver:1809
NETCORE_BUILD_IMAGE=mcr.microsoft.com/dotnet/sdk:6.0-nanoserver-1809
NETCORE_RELEASE_IMAGE=mcr.microsoft.com/dotnet/aspnet:6.0-nanoserver-1809

Once you run it, you may see a list of containers – it is pretty similar to what you can find with the classical XM platform:

xm cloud containers

There is a Solr container used for internal purposes, a container with a SQL server, and init containers for both of them. The main container is called CM, but please be aware (mainly for patching purposes) that the role name is not CM any longer but is XMCloud instead – here’s its definition:

<add key="role:define" value="XMCloud" />

Another important container here is Traefik – the one that accepts and distributes external traffic. One of the most frequent errors occurring while trying to set up local containers is that developers often already have a running web server (for example IIS) that occupies port 80, preventing Traefik running in a container from exposing its own port 80, so that it errors out.

The rest of the containers are providing rendering hosts with “head” applications.

The principal difference of local container development is that instead of using Edge from XM CLoud, your rendering host will connect to the GraphQL endpoint of the CM instance which features the same API as cloud-based Experience Edge (if you’re using Headless SXA then Edge endpoint is configured in the site settings item).

What can a developer control?

You can apply config patches for configuring CM instance, the same as you did before with XP (here's an example). There’s also a guide on deploying customizations to the XM Cloud environment.

That means it is possible to turn things on and off, like overriding pipeline processors. Of course, there is no longer request manipulation with XM Cloud as request processing shifts to the rendering host while httpRequestBegin and httpRequestEnd pipelines relate only to the CM instance itself. Nevertheless, there are lots of other familiar pipelines to deal with.

For your custom code Sitecore NuGet Feed will support relevant packages. All the packages follow up the name pattern starting with Sitecore.XMCloud.* so that one cannot mess them up with the packages for XP/XM platforms. For example, the top-level package is called Sitecore.XMCloud.Platform. Versioning of these packages follows up SemVer version of XMCloud.

Sitecore has an expectation that developers customize it less and less with time, with most of the customization happening around data synchronization rather than the altering system itself.

One of the good news is that you still have the PowerShell Extensions, but that is disabled by default. Enabling it requires setting SITECORE_SPE_ELEVATION environment variable to either Confirm or Allow with new deployment to take effect. Once complete – you get full power, including direct database access. I recorded a short video showing how to enable SPE for your XM Cloud environment, if you also need SPE remoting - read this post for additional instructions.

Sitecore Connect for Content Hub: CMP or DAM connector provided by XM Cloud, is also included in the base container image. There is a promise of adding connectors to other popular systems and DXPs with time.

Unfortunately, installing familiar modules is no longer available in the easy way of package installation we used to.

Build configuration

There is a new important configuration file with XM Cloud – xmcloud.build.json. It configures build targets, editing hosts, XDT transforms, if any, serialization modules to deploy to the XM instance for that environment, etc. Its structure looks as below:

{
  "deployItems": {
    "modules": []
  },
  "buildTargets": [],
  "renderingHosts": {
    "<key>": <value>
  },
  "transforms": [],
  "postActions": {
    "actions": {
      "<key>": <value>
    }
  }
}

For example, the default build pipeline of XM Cloud is configured to look at a single *.sln file at the root of your repository to process, but with xmcloud.build.json a developer can override this behavior and specify what exactly to build.

Read more about configuring build configuration with XM Cloud.

Sitecore CLI

With Sitecore XP most of you likely used CLI for serialization purposes, with some rare developers experimenting with itemres plugin for creating items as resources from serialization modules. With XM Cloud Sitecore CLI becomes much more useful – lots of management could (and should) be done using it, like creating the projects and environments, performing initial deployments using CLI, etc.

In order to install it you must have .NET 6 already installed on your machine. The tool consists of two parts – Sitecore Management Services running on CM and the command line tool itself. Management Services comes already preinstalled for XM Cloud (in both development container and cloud instance), so it only comes to installing CLI from the project folder:

dotnet nuget add source -n Sitecore https://sitecore.myget.org/F/sc-packages/api/v3/index.json
dotnet tool install Sitecore.CLI

Here’s an example of using CLI to list projects running against my XM Cloud organization (subscription).

CLI shows list of projects

In the XM Cloud terminology, a project is a set of environments. Each environment is in fact its own XM Cloud CM instance. Therefore, we can list a set of its environments and can take actions towards each individual environment, let’s say publish its content to Experience edge:

dotnet sitecore publish --pt Edge -n <environment-name>

Old good serialization also works well with CLI in order to synchronize items between remote and local XM Cloud instances:

dotnet sitecore serialization pull -n development
dotnet sitecore serialization push -n development

Similarly to 10.x platforms, you may benefit from a Sitecore for Visual Studio if you prefer using GUI for Sitecore Content Serialization. It is not free to use and requires purchasing a TDS license (notably, TDS itself does not support XM Cloud).

One of its greatest features in my opinion is Sitecore Module Explorer for you you to navigate, create and modify Sitecore Content Serialization configuration files as items in a tree structure.

Head Application

Configuring front-end applications mainly comes to these 3 settings:

  • JSS_APP_NAME – the name of a site
  • GRAPH_QL_ENDPOINT should be pointing to Edge GraphQL
  • SITECORE_API_KEY for Edge Token, typically found under /sitecore/system/Settings/Services/API Keys

I would recommend watching this video for the front-end developer setup for XM Cloud.

The really impressive productivity feature is running npm run start:connected where you set up a watch mode against the source code so that changes get immediately updated in the browser.

You can also configure an external editing host for XM Cloud instances so that you could also benefit from editing experiences in Pages.

Edge and GraphQL

There is a common misunderstanding about content published to Experience Edge becoming unprotected. It comes from the official documentation, saying: “Experience Edge for XM does not enforce security constraints on Sitecore content. You must apply publishing restrictions to avoid publishing content that you do not want to be publicly accessible“. In fact, publishing to Edge is not equal to making content publically exposed – no one can access it without providing a valid Edge Token you typically configure as SITECORE_API_KEY parameter for your environment. API Key should never be shared publicly – each time you need to pull the data out of Edge you should delegate that task to your head application in order to obscure API Keys rather than making such calls directly from JavaScript code running in the client’s browser, thus exposing your secrets.

Previously with XP/XM it was possible to extend the GraphQL Preview endpoint for additional features like comparison for integers and dates, coordinates, multifield searches, faceting, etc. It is worth mentioning that now you cannot perform any changes around GraphQL endpoints via pipelines with XM Cloud, because it uses Edge. You can still do the following operators in addition to searching fields by a value: compare with EQ / NEQ, CONTAINS / NCONTAINS, order by field names both ways, do pagination, logical AND, and OR.

You can read more about Edge Schema at this link. Also, it is worth mentioning that Edge has its own webhooks. This can be helpful for notifying a caller when publishing is complete.

Use New-EdgeToken.ps1 script to create one for any desired environment. Upon completion, this script opens up GraphQL IDE automatically as well as returns X-GQL-Token for you to use with IDE straight away.

Note that you can use the GraphQL IDE only if you have installed Experience Edge. However, Edge tenant and edge connecter are created automatically upon the creation of an environment with XM Cloud.

Another thing to keep in mind is that despite you're empowered to use Media Library, there's a GraphQL limitation of 50Mb per resource. For larger files (likely videos), consider using DAMs, given they typically allow wider management options.

XM Cloud Play! Summit Demo

This is an exceptional gift from Sitecore Demo Team to us! It allows you to experiment working with XM Cloud and its features, and editing capabilities in both local containers and by deploying it to your XM Cloud environment.

The demo solution features many of the latest tech working together for us to play with:

  • Sitecore XM Cloud
  • Content Hub DAM and CMP
  • Headless SXA
  • JavaScript Services (JSS)
  • Next.js
  • Vercel
  • Tailwind CSS

Since the demo is available in the GitHub repository which means it is easily deployable with Deploy App in a few clicks, as demonstrated below.

Headless SXA

XM Cloud comes with a built-in headless SXA as well as it is included in the base XM Cloud container image, so the question comes up – should I go with it or without it?

Of course, it is possible to build a headless app on XM Cloud without using SXA, but it is not recommended. SXA provides many benefits and is included by default with Sitecore XM Cloud, so it is generally a better option for building a new site. If you are in a migration scenario, you may not have the option to use SXA, but other than that, it is recommended to use SXA and headless services for the best results.

There is an XM Cloud Starter Kit with Next JS for your faster journey to XM Cloud development.

Rendering Variants was always one of the most powerful features of SXA and it took its own evolution from NVelocity templates to scriban, now became powered with Next.js – here’s an example of a Promo component having two rendering variants – Default and WithText. You can have as many variants other than Default as you want within a *.tsx file following this syntax:

export const Default = (props: PromoProps): JSX.Element => {
  if (props.fields) {
    return (
    // ... markup merged with props 
    );
  }
  return <PromoDefaultComponent {...props} />;
};

SXA is perfect for multi-site implementations as it comes with cross-site capabilities for sharing page and partial designs, renderings, content, and cross-linking. In a previous post, I already mentioned a new feature – Page Branches – that allow setting standard values for layout on a per-website basis. Site-specific standard values feature is another example of managing default on a per-site basis. The development team is also working on implementing Site Templates – the idea comes about blueprinting the entire sites from pre-defined templates.

Speaking about the UI – by default, SXA comes with the grid system Bootstrap 5, but that is configurable. Default renderings respect both grid and styles through parameter templates, you must care of that once creating your own custom renderings unless, of course, you clone existing ones.

I remember a while ago when I only started working with SXA there was a lack of proper solutions around renderings having a background image set – we had to invent our own approaches to that. Slightly later SXA got that feature, but today I was glad to find support for a decent number of stretch modes with it: parallax, stretch, vertical and horizontal tiles, fixed. And all that works in headlessly!

Deploy App

Sitecore XM Cloud comes with an integrated Deploy App that performs exactly what is called for – deploying your solution using existing source code to XM Cloud from a friendly GUI as an alternative to using CLI. You can also create a project using a starter template.

choosing deployment method

In order to demonstrate its capabilities, let’s Start from your existing XM Cloud code, using Play! Summit demo mentioned earlier. Currently, the only provider to work with Deploy App is GitHub. So let’s go ahead with it.

deployment with github

After providing access to your GitHub account, you configure and choose the branch and the specific environment it deploys to if it is production or not. There is an option to trigger a deployment on commit to that branch to trigger an automatic deployment each time someone makes a push to the specified repository branch.

deployment with github parameters

That’s it! The deployment starts and makes things done automatically: provisioning tenants and environments, configuring Edge, pulling and building the codebase, deploying the artifacts, and eventually running post-build actions, if any.

deployment log

The whole process takes around 15 minutes and is impressive – the whole CI/CD pipeline comes out of the box, preconfigured. However, there’s one more thing left to do.

Deploying to Vercel

Since our head application is decoupled from Sitecore, we also need to deploy and reference it to Experience Edge. There are various options but the best would be using Vercel hosting. Vercel is the company that invented and maintains Next.js which is why their powerful hosting platform ideally fits solutions built with it.

Vercel is an all-in-one development platform that combines the best developer experience with an obsessive focus on end-user performance. As a developer-centric stack, Vercel is accessible to developers of all skill sets and removes a historical lock-in to .NET. Vercel provides a scalable solution to the largest of organizations with the newest best practices in content delivery while helping to reduce infrastructure/deployment overhead previously required to deploy Sitecore applications.

Deployment is pretty simple. Upon creating an account and project, you link your GitHub account and use the same repository used for XM Cloud deployment previously. Vercel is smart enough to autosuggest a folder with your next.js application highlighting it with an icon. After providing three familiar environmental parameters (App name, Edge endpoint, and API key for Edge token) deployment takes place quickly and then you’re all set!

Tip: do not modify .env file directly, instead - create an "override" file called .env.local. The difference is that in starterkits that file by default is excluded from source control with .gitignore, plus in addition to preventing mess with other developers, you can easily maintain your specific environmental setting in the same place.

XM Cloud also allows you to set up a Vercel hosting directly from its interface and add integration.

vercel setup hosting

If you do not have an account - you'll also be able to create one with quickly and easily, currently, there's only support for Personal Account types. Also, make sure you choose All Project access.

vercel link to vercel

You will still need to log in to Vercel and manually add GitHub integration to the same repository and branch, add a few more parameters, and at least 3 above environmental variables. Once done you can immediately trigger the deployment.

I hope this overview helps you familiarize XM Cloud development options and encourages you to try it earlier rather than later. Would you have any questions – please feel free reaching me!

Sitecore Edge considerations for sitemap

A quick one today. We recently came across interesting thoughts and concerns about using Sitecore Edge. As you might know (for example from my previous post), there are no more CD servers when publishing to Sitecore Edge - think of that as just a GraphQL endpoint serving out json.

So, how do we implement a sitemap.xml in such a case? Brainstorming brought several approaches to consider:

Approach one

  • Create a custom sitemap NextJS route
  • Use GraphQL to query Edge using the search query. Here we would have to iterate through items in increments of 10
  • Cache the result on Vercel side using SSG

Approach two

  • Create a service from CM side that will return all published items/urls
  • This service will only be accessible by Azure function which will generate a sitemap file and store it in CDN
  • Front-end would then in this case access this file and render the content of it (or similar)

Approach three

  • Generate all the sitemaps (if more than a single sitemap) on CM, then store them all in single text fields
  • Returned them via edge, using GraphQL the font-end head which handles sitemap.xml

Then I realized, there is SXA Headless boasts SEO features OOB, including sitemap.xml. Let's take a look at what they do in order to generate sitemaps.

With 10.3 of SXA, the team has revised the Sitemap feature providing much more flexibility to cover as many use cases as only possible. Looking at /Sitecore/Content/Tenant/Site/Settings/Sitemap item you'll find lots of settings for fine-tuning your sitemaps depending on your particular needs. CM crawls websites and generates sitemaps. Then they get published to Sitecore Edge as a blob and then it gets proxied by a Rendering Host via GraphQL. When search engines request sitemaps of a particular website, Rendering Host gives them exactly what has been asked. That is actually similar to the above approach three with all the invalidation and updates of sitemaps provided also OOB.

This gives out a good amount of options, depending on your particular scenario.

My speech proposal for SUGCON ANZ: Developers' guide to XM Cloud

Developers' guide to XM Cloud

Over the last months, we've heard lots of insights about the newest cloud product from Sitecore - XM Cloud. Many developers have wondered about what would be their changes of scope and responsibilities, how would they work with this new SaaS solution, or whether would they even become redundant. 

There is nothing to worry about! This session is answering most of these questions and in fact comes as the most crucial developers' guide to XM Cloud, as for now. It explains the caveats of changes in the content lifecycle and local development routine, introduced new Headless SXA for XM Cloud, and new options for personalization. It will also highlight changes in the security model, and site search and give the best advice on legacy strategies and content migration. Finally, some practical experience with Headstart for XM Cloud and utilizing the new deployment model, so that it becomes live!

Getting started
  •     why SaaS cloud? what is the change?
  •     a brief overview of XM Cloud for developers    
Familiar tools that remain with you
  •     review of the process and deployment tools available to a developer
  •     local development for XM Cloud in containers
  •     customizing pipelines with XM Cloud
  •     leveraging SPE functions
  •     Sitecore CLI becomes "The Tool"
Editing Experience and Content Considerations:
  •     using Site Builder
  •     dealing with Pages & Components
  •     extensions catalog
  •     diversity of datasources: where can my content reside?
  •     migrating content from legacy Sitecore platforms
Changes in the security model
  •     Sitecore Unified Identity
  •     integrating 3-rd party services
Changes in search
  • where's my Solr?
  • what are the options?
  • plugging an external search technology
Dealing with the legacy
  •     are my legacy sites still compatible with XM Cloud?
  •     migrating headless site from XP to XM Cloud guidance
  •     EDGE considerations
  •     is my legacy module for XP compatible with XM Cloud?

SXA for XM Cloud
  •     new old Headless SXA - what's the difference
  •     new old rendering variants
  •     can we use headless forms on XM Cloud?
  •     a bare minimum to build Headless SXA site for Next.js
Hands-On
  •     starter kits available for you straight away
  •     leveraging Headstart basic starter kit foundation built for XM Cloud
  •     make your own module compatible with XM Cloud
Personalization
  •     are the built-in rules enough to go?
  •     two ways of leveraging CDP/Personalize for a better experience
Deploying into XM Cloud
  •     Single-location? Will that affect my GEO-distributed authors team?
  •     Understanding terminology: deployment, project, environment
  •     Understanding Build and Deployment Service (how to trigger and its lifecycle)
  •     CLI along with DevEx plugins
  •     GUI-powered Deploy App tool and 
  •     auto deployments from connected GitHub
It looks to me like an excellent topic exposing a spotlight on the new Sitecore SaaS-based platform. Keep fingers crossed!