Experience Sitecore ! | All posts tagged 'XM Cloud'

Experience Sitecore !

More than 200 articles about the best DXP by Martin Miles

XM Cloud content migration: connecting external database

Historically when performing content migration with Sitecore we used to deal with database backups. In a modern SaaS world, we do not have the luxury of neither managing cloud database backups, nor the corresponding UI for doing this. Therefore, we must find an alternative approach.

Technical Challenge

Let’s assume we have a legacy Sitecore website, in my case that was XP 9.3 and we’ve been provided with only a master database backup having all the content. The objective is to perform content migration from this master database into a new and shiny XM Cloud environment(s).

Without having direct access to the cloud, we can only operate locally. In theory, there could be a few potential ways of doing this:

  1. Set up a legacy XP of the desired version with the legacy content database already attached/restored to it. Then try to attach (or restore) a vanilla XM Cloud database to a local SQL Server as a recipient database in order to perform content migration into it. Unfortunately, the given approach would not work since SQL Server version incompatibility between XM Cloud and XP 9.3. Even if that was possible, running XP 9.3 with the XM Cloud database won’t work as Xз 9.3 neither knows about XM Cloud schema nor is capable of handling Items as Resource required feature which was invented later in XP 10.1. Therefore – this option is not possible.

  2. Can we go the other way around by using the old database along with XM Cloud? This is not documented, but let’s assess it:

    1. Definitely won’t work in the cloud since we’re not given any control of DBs and their maintenance or backups.

    2. In a local environment, XM Cloud only works in Docker containers and it is not possible to use it with an external SQL Server where we have a legacy database. But what if we try to plug that legacy database inside of the local SQL Container? Sadly, there are no documented ways of achieving that.

  3. Keep two independent instances side by side (legacy XP and XM Cloud in containers) and use an external tool to connect both of them in order to migrate the content. In theory that is possible but carries on few drawbacks.
    1. The tool of choice is Razl, but this tool is not free, requires a paid license, and does not have a free trial to ever test this out.
    2. Connecting to a containerized environment may not be easy and require some additional preps
    3. You may need to have a high-spec computer (or at least two mid-level machines connected to the same network) to have both instances running side by side.

After some consideration, the second approach seems to be reasonable to try so let’s give it a chance and conduct a PoC.

Proof of Concept: local XM Cloud with external content database

Utilize the second approach we’re going to try attaching the given external legacy database to XM Cloud running in a local containerized setup. That will allow using a built-in UI for mass-migrating the content between the databases (as pictured below) along with the Sitecore PowerShell script for finalizing and fine-tuning the migrated content.

Control Panel

Step 1: Ensurу SQL Server port is externally exposed

We are connecting the external SQL Server Management studio through a port of the SQL Server container that is exposed externally in order to make it possible. Luckily, that has been done for us already, just make sure docker-compose has:

ports:
      - "14330:1433"

Step 2: Spin up an XM Cloud containers and confirm XM Cloud works fine for you

Nothing extraordinary here, as easy as running .\init.ps1 followed by .\up.ps1.

Step 3: Connect SQL Management Studio to SQL Server running in a container.

After you sound up containers, run SQL Management Studio and connect to SQL Server running in SQL container through an exposed port 14330, as we did at step 1:

Connection parameters

Step 4: Restore the legacy database

If you have a Data-Tier “backpack” file you may want to do an extra step and convert it into a binary backup for that particular version used by XMCloud before restoring. This step is optional, but in case you want to restore the backup more than once (which is likely to happen), it would make sense to take a binary backup as soon as you restore the data-tier “backpack” first time ever. Data-tier backups process much slower than binaries, so that will definitely save time in the future.

Once connected, let’s enable contained database authentication. This step is mandatory, otherwise, that would not be possible to restore a database:

EXEC sys.sp_configure N'contained database authentication', N'1'
go
exec ('RECONFIGURE WITH OVERRIDE')
go

One more challenge ahead: when performing backup and restore operations, SQL Server shows up a path local to the server engine, and not the host machine. That means, our backup should exist “inside” of SQL container. Luckily, w have this also covered. Make sure docker-compose.override.yml contains:

mssql:
volumes:
      - type: bind
source: .\docker\data\sql
target:c:\data

That means, one can locate legacy database backups into .\docker\data\sql folder of a host machine and it will magically appear within C:\datafolder when using SQL Management Studio database backup restore tool which you can perform now.

Important! Restore legacy database using the “magic name” in a format Sitecore.<DB_NAME_SUFFIX>, further down below I will be using the value RR as DB_NAME_SUFFIX.

Once got restored database in SQL Server Management Studio under the name Sitecore.RR we need to plug this database to the system. There is a naming convention hidden from our eyes within CM containers.

Step 5: Configure connection strings

Unlike in XM/XP – there is no documented way to plug an external database. The way connection strings are mapped to the actual system is cumbersome, it uses some “magic” hidden within the container itself and obfuscated from our eyes. It only tool to reach it experimental way. Here are the steps to reproduce:

  • Add environmental variable to docker-compose record for CM:

    • Sitecore_ConnectionStrings_RR: Data Source=${SQL_SERVER};Initial Catalog=${SQL_DATABASE_PREFIX}.RR;User ID=${SQL_SA_LOGIN};Password=${SQL_SA_PASSWORD}
  • Add a new connection string record. To do so you’ll need to create a connection strings file within your customization project as .\src\platform\<SITENAME>\App_Config\ConnectionStrings.config with the content of the connection strings file from the CM container with the addition of a new string:

Please note the difference in the suffix format of both above records, that is totally fine. CM container still processes that correctly.

Step 6: Reinstantiating CM container

Simply restarting a CM container is not sufficient. You must remove it and re-create it, just killing/stopping is not sufficient.

For example, the below command will work for that purpose:

docker-compose restart cm

… not will this one:

docker-compose kill cm

The reason is that CM will not update environmental variables from docker-compose file upon restart. Do this instead:

docker-compose kill cm
docker-compose rm cm --force
docker-compose up cm -d

Step 7: Validating

  1. Inspecting CM container for environmental variables will show you this new connection string, as added:

    1. "Env": [
                  "Sitecore_ConnectionStrings_RR=Data Source=mssql;Initial Catalog=Sitecore.RR;User ID=sa;Password=6I7X5b0r2fbO2MQfwKH"
  2. Inspecting connection string config (located at C:\inetpub\wwwroot\App_Config\ConnectionStrings.config on CM container) contains the newly added connection string.

Step 8: Register new database with XM Cloud

It can be done the below config patch that does this job. Save it as docker\deploy\platfo.rm\App_Config\Include\ZZZ\z.rr.config for test and later do not forget to include it in a platform customization project, so that it gets shipped with each deployment
<?xml version="1.0" encoding="UTF-8"?>
<configuration
	xmlns:patch="www.sitecore.net/.../">
	<sitecore>
		<eventingdefaultProvider="sitecore">
			<eventQueueProvider>
				<eventQueuename="rr"patch:after="evertQueue[@name='web']"type="Sitecore.Data.Eventing.$(database)EventQueue, Sitecore.Kernel">
					<paramref="dataApis/dataApi[@name='$(database)']"param1="$(name)"/>
					<paramref="PropertyStoreProvider/store[@name='$(name)']"/>
				</eventQueue>
			</eventQueueProvider>
		</eventing>
		<PropertyStoreProvider>
			<storename="rr"patch:after="store[@name='master']"prefix="rr"getValueWithoutPrefix="true"singleInstance="true"type="Sitecore.Data.Properties.$(database)PropertyStore, Sitecore.Kernel">
				<paramref="dataApis/dataApi[@name='$(database)']"param1="$(name)"/>
				<paramresolve="true"type="Sitecore.Abstractions.BaseEventManager, Sitecore.Kernel"/>
				<paramresolve="true"type="Sitecore.Abstractions.BaseCacheManager, Sitecore.Kernel"/>
			</store>
		</PropertyStoreProvider>
		<databases>
			<databaseid="rr"patch:after="database[@id='master']"singleInstance="true"type="Sitecore.Data.DefaultDatabase, Sitecore.Kernel">
				<paramdesc="name">$(id)
				</param>
				<icon>Images/database_master.png</icon>
				<securityEnabled>true</securityEnabled>
				<dataProvidershint="list:AddDataProvider">
					<dataProviderref="dataProviders/main"param1="$(id)">
						<disableGroup>publishing</disableGroup>
						<prefetchhint="raw:AddPrefetch">
							<sc.includefile="/App_Config/Prefetch/Common.config"/>
							<sc.includefile="/App_Config/Prefetch/Webdb.config"/>
						</prefetch>
					</dataProvider>
				</dataProviders>
				<!-- <proxiesEnabled>false</proxiesEnabled> -->
				<archiveshint="raw:AddArchive">
					<archivename="archive"/>
					<archivename="recyclebin"/>
				</archives>
				<cacheSizeshint="setting">
					<data>100MB</data>
					<items>50MB</items>
					<paths>2500KB</paths>
					<itempaths>50MB</itempaths>
					<standardValues>2500KB</standardValues>
				</cacheSizes>
			</database>
		</databases>
	</sitecore>
</configuration>

Step 9: Enabling Sitecore PowerShell Extension

Next, we’d want to enable PowerShell, if that is not yet done. You won’t be able to migrate the content using SPE without performing this step.

<?xml version="1.0" encoding="utf-8"?>
<configuration
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:role="http://www.sitecore.net/xmlconfig/role/"
	xmlns:set="http://www.sitecore.net/xmlconfig/set/">
	<sitecorerole:require="XMCloud">
		<powershell>
			<userAccountControl>
				<tokens>
					<tokenname="Default"elevationAction="Block"/>
					<tokenname="Console"expiration="00:55:00"elevationAction="Allow"patch:instead="*[@name='Console']"/>
					<tokenname="ISE"expiration="00:55:00"elevationAction="Allow"patch:instead="*[@name='ISE']"/>
					<tokenname="ItemSave"expiration="00:55:00"elevationAction="Allow"patch:instead="*[@name='ItemSave']"/>
				</tokens>
			</userAccountControl>
		</powershell>
	</sitecore>
</configuration>

Include the above code into a platform customization project as .\docker\deploy\platform\App_Config\Include\ZZZ\z.SPE.config. If everything is done correctly, you can run SPE commands, as below:

SPE results

The Result

After all the above steps are done correctly, you will be able to utilize the legacy content database along with your new shiny local XM Cloud instance:
Result in Sitecore Content Editor
Now you can copy items between databases just by using built-in Sitecore UI preserving their IDs and version history. You can also copy items with SPE from one database to another which are both visible to the SPE engine.

Full guide to enabling Headless Multisite Add-On for your XM Cloud solution

Sitecore Headless Next.Js SDK recently brought the feature that makes it possible to have multiple websites from Sitecore content tree being served by the same rendering hoist JSS application. It uses Next.js Middleware to serve the correct Sitecore site based on the incoming hostname.

Why and When to Use the Multisite Add-on

The Multisite Add-on is particularly useful in scenarios where multiple sites share common components or when there is a need to centralize the rendering logic. It simplifies deployment pipelines and reduces infrastructure complexity, making it ideal for enterprises managing a portfolio of websites under a unified architecture. This approach saves resources and ensures a consistent user experience across all sites.

How it works

The application fetches site information at build-time, not at runtime (for performance reasons). Every request invokes all Next.js middleware. Because new site additions are NOT frequent, fetching site information at runtime (while technically possible) is not the best solution due to the potential impact on visitor performance. You can automate this process using a webhook to trigger automatic redeployments of your Next.js app on publish.

Sitecore provides a relatively complicated diagram of how it works (pictured below), but if you do not get it from the first look, do not worry as you’ll get the understanding after reading this article.

Uuid 8601bf89 50ab 5913 42ed 4773b05ab2c3[1]

Technical Implementation

There are a few steps one must encounter to make it work. Let’s start with the local environment.

Since multisite refers to different sites, we need to configure hostnames. The Main site operates on main.localhost hostname, and it is already configured as below:

.\.env
    RENDERING_HOST=main.localhost
.\docker-compose.override.yml
    PUBLIC_URL: "https://${RENDERING_HOST}"

For the sake of the experiment, we plan to create a second website served by second.localhost locally. To do so, let’s add a new environmental variable to the root .env file (SECOND_HOST=second.localhost) and introduce some changes to init.ps1 script:

$renderingHost = $envContent | Where-Object{$_ -imatch "^RENDERING_HOST=.+"}
$secondHost = $envContent | Where-Object{$_ -imatch "^SECOND_HOST=.+"}
$renderingHost = $renderingHost.Split("=")[1]
$secondHost = $secondHost .Split("=")[1]

down below the file we also want to create SSL certificate for this domain by adding a line at the bottom:

& $mkcert -install
# & $mkcert "*.localhost"
& $mkcert"$xmCloudHost"
& $mkcert"$renderingHost"
& $mkcert"$secondHost"

For Traefic to pick up the generated certificate and route traffic as needed, we need to add two more lines at the end of .\docker\traefik\config\dynamic\certs_config.yaml file:

tls:
certificates:
    - certFile:C:\etc\traefik\certs\xmcloudcm.localhost.pem
keyFile:C:\etc\traefik\certs\xmcloudcm.localhost-key.pem
    - certFile:C:\etc\traefik\certs\main.localhost.pem
keyFile:C:\etc\traefik\certs\main.localhost-key.pem
    - certFile:C:\etc\traefik\certs\second.localhost.pem
keyFile:C:\etc\traefik\certs\second.localhost-key.pem

If you try initializing and running – it may seem to work at first glance. But navigating to second.localhost in the browser will lead to an infinite redirect loop. Inspecting the cause I realized that occurs due to CORS issue, namely that second.localhost does not have CORS configured. Typically, when configuring the rendering host from docker-compose.override.yml we provide PUBLIC_URL environmental variable into the rendering host container, however, that is a single value and we cannot pass multiple.

Here’s the more descriptive StackOverflow post I created to address a given issue

To resolve it, we must provide the second host in the below syntax as well as define CORS rules as labels into the rendering host, as below:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.rendering-secure.entrypoints=websecure"
  - "traefik.http.routers.rendering-secure.rule=Host(`${RENDERING_HOST}`,`${SECOND_HOST}`)"
  - "traefik.http.routers.rendering-secure.tls=true"
# Add CORS headers to fix CORS issues in Experience Editor with Next.js 12.2 and newer
  - "traefik.http.middlewares.rendering-headers.headers.accesscontrolallowmethods=GET,POST,OPTIONS"
  - "traefik.http.middlewares.rendering-headers.headers.accesscontrolalloworiginlist=https://${CM_HOST}, https://${SECOND_HOST}"
  - "traefik.http.routers.rendering-secure.middlewares=rendering-headers"

Once the above is done, you can run the PowerShell prompt, initialize, and spin up Sitecore containers, as normal, by executing init.ps1 and up.ps1 scripts.

Configuring Sitecore

Wait until Sitecore spins up, navigate to a site collection, right-click it, and add another website calling it Second running on a hostname second.localhost.

Make sure second site uses the same exact application name as main, you can configure that from /sitecore/content/Site Collection/Second/Settings item, App Name field. That ensures the purpose of this exercise is for both sites to reuse the same rendering host application.

You should also make sure to match the values of the Predefined application rendering host fields of /sitecore/content/Site Collection/Second/Settings/Site Grouping/Second and /sitecore/content/Site Collection/Main/Settings/Site Grouping/Main items.

Another important field here is Hostname, make sure to set these fields for both websites:

Hostname

 

Now you are good to edit Home item of Second site. Multisite middleware does not affect the editing mode of Sitecore, so from there, you’ll see no difference.

Troubleshooting

If you’ve done everything right, but second.localhost resolves to the Main website, let’s troubleshoot. The very first location to check is .\src\temp\config.js file. This file contains sites variable with the array of sites and related hostnames to be used for the site resolution. The important fact is that a given file is generated at the build time, not runtime.

So, you open it up and see an empty array (config.sites = process.env.SITES || '[]') that means you just need to initialize the build, for example by simply removing and recreating the rendering host container:

docker-compose kill rendering
docker-compose rm rendering -f
docker-compose up rendering -d

Also, before running the above, it helps to check SXA Site Manager, which is available under the PowerShell Toolbox in Sitecore Desktop. You must see both sites and relevant hostnames there and in the correct order – make sure to move them as high as possible, as this site chain works by “first come – first served” principle.

Multisite

After rendering host gets recreated (it may take a while for both build and spinning up steps), check the site’s definition at .\src\temp\config.js again. It should look as below:

config.sites = process.env.SITES || '[{"name":"second","hostName":"second.localhost","language":""},{"name":"main","hostName":"main.localhost","language":""}]'

The amount of SXA client site records must match the records from SXA Site Manager. Now, running second.localhost in the browser should show you rendered home page of the second site.

Another technique of troubleshooting is to inspect middleware logs. To do so, create .env.local file at the rendering host app root (if does not exist yet) and add the debugging parameter:

DEBUG=sitecore-jss:multisite

Now, rendering host container logs will expose you the insights into how multisite middleware processes your requests and resolves site contexts, and sets site cookies. Below is a sample (and correct) output of the log:

sitecore-jss:multisite multisite middleware start: { pathname: '/', language: 'en', hostname: 'second.localhost' } +8h
sitecore-jss:multisite multisite middleware end in 7ms: {
  rewritePath: '/_site_second/',
  siteName: 'second',
  headers: {
  set-cookie: 'sc_site=second; Path=/',
  x-middleware-rewrite: 'https://localhost:3000/_site_second',
  x-sc-rewrite: '/_site_second/'
},
  cookies: ResponseCookies {"sc_site":{"name":"sc_site","value":"second","path":"/"}}
} +7ms

The above log is crucial to understanding how multisite middleware works internally. The internal request comes rewrites as <https://localhost:3000/_site_second where ‘second‘ is a tokenized site name parameter. If .\src\main\src\temp\config.js file contains the corresponding site record, site gets resolved and sc_site cookie is set.

If you still have the issues of showing up the Second website that resolves to Main website despite the multisite middleware log outputs correct site resolution, that may be likely caused by conflicting with other middleware processors.  This would be your number one thing to check especially if you have multiple custom middleware. Miltisie middleware is especially sensitive to the execution order, as it sets cookies. In my case, that problem was because Sitecore Personalize Engage SDK was registered through middleware and also programmed to set its own cookie, and somehow configured with multisite middleware.

In that case, you have to play with order constant within each conflicting middleware (they are all located under .\src\lib\middleware\plugins folder) with the following restart of a rendering host.

Resources Sharing

Since the multisite add-on leverages the same rendering host app for the multiple sites that use it, all the components and layouts TSX markups, middleware, and the rest of the resources become automatically available for the second site. However, that is not true by default for the Sitecore resources. We must assign at least one website that will share its assets, such as renderings, partials, layouts, etc across the entire Site Collection. Luckily, that is pretty easy to do at the site collection level:

Site Collection

For the sake of an experiment, let’s make Second website to use Page Designs from the Main site. After the above sharing permission, they are automatically available at /sitecore/content/Site Collection/Second/Presentation/Page Designs item.

Configuring the cloud

Once we make the multisite add-on function well, let’s make the same work in the cloud.

  1. First of all, check all the changes to the code repository, and trigger the deployment, either from Deploy App, CLI, or your preferred CI/CD
  2. After your changes arrive at XM Cloud environment – let’s bring the required changes to Vercel, starting with defining the hostnames for the sites:Vercel
  3. After you define the hostnames in Vercel, change the hostname under Site Grouping item for this particular cloud environment to match the above hostname configured earlier in Vercel.
  4. Save changes and smart publish Site Collection. Publishing takes some time, so please stay patient.
  5. Finally, you must also do Vercel full re-deployment, to force regenerating \src\temp\config.js file with the hostnames from Experience Edge published at the previous step

Testing

If everything is done right in the previous steps, you can access the published Second website from Vercel by its hostname, in our case that would be https://dev-second.vercel.app. Make sure all the shared partial layouts are seen as expected.

When testing 404 and 500 you will see that these are shared between both sites automatically, but you cannot configure them individually per site. This occurs because both are located at .\src\pages\404.tsx and .\src\pages\500.tsx of the same rendering host app and use different internal mechanics rather than generic content served from Sitecore throughout .\src\pages\[[...path]].tsx route. These error pages however could be multi-lingual, if needed.

Hope you find this article useful!

Using conditions with XM Cloud Form Builder

If you follow the release news from Sitecore, you’ve already noted the release of highly awaited XM Cloud Forms, which however did not have a feature of adding conditional logic. Until now.

The good news is that now we can do it, and here’s how.

See it in action

Imagine you have a registration form and want to ask if your clients want to receive the email. To do so you add two additional fields at the bottom:

  • a checkbox for users to define if they want to receive these emails
  • a dedicated email input field for leaving the email address

Obviously, you want to validate the email address input as normal and make it required. At the same time you want this field to only play on with a checkbook being checked, otherwise being ignored and ideally hidden.

Once we set both Required and Hidden properties, there is a hint appears saying that we cannot have them both together as it simply creates a deadlock – something I mentioned in my earlier review of XM Cloud Forms builder

01

So, how we achieve that?

From now on, there is an additional Logic tab that you can leverage to add some additional logic to your forms.

02

Let’s see what can do with it:

  • you can add new pieces of logic
  • apply multiple conditions within the piece of logic and define if all must comply or just any of them (logical “AND” and “OR”)
  • you can create groups of conditions to combine multiple OR logic clauses to work against the same single AND condition
  • for each condition, select a particular field of application from a dropdown
  • define what requirements you want to meet with it from another dropdown, which is content-specific:
    • strict match
    • begins or ends with
    • contains
    • checked / unchecked
    • etc.
  • not just having multiple conditional logic, you can also have multiple fields logic within a single condition with the same “and / “or”

Once all conditions are met, you execute the desired action against a specified field:

04

Coming back to a form with an optional email subscription defined by a checkbox, I created the following rule:

05

The conditional logic rules engine is very much intuitive and human-readable. I do not even need to explain the above screenshot as it is naturally self-explanatory. So, how does it perform? Let’s run Preview and see it in action.

When running the form as normal, we only see a checkbox disabled by default and can submit it straight away:

06

But checking Email me updates and promotions tick enables Email field, which is Required. The form won’t submit with an invalid email address until the checkbox is checked.

07

My expectation for the conditional logic was applying conditions for multipage forms at the page level. Say, I have a first page having some sort of branching condition, so that if the user meets it – it gets to pages 2 and 3, but if not – only page 4, with both branching ending up at page 5. Unfortunately, I did not find a way of doing that. Hopefully, the development teams will add this in the future, given how progressively and persistently they introduce new features. In any case, what we’ve been given today is already a very powerful tool that allows us to create really complicated steams of input user data.

XM Cloud Forms Builder

XM Cloud Forms Builder released

Composable Forms Builder is now available with Sitecore XM Cloud. Let’s take a look at this one of the most anticipated modules for Sitecore’s flagship hybrid headless cloud platform.

Historically, we had an external module called Web Forms for Marketers that one could install on top of their Sitecore instance in order to gain the desired functionality of collecting user input. This module was later well reconsidered and reworked, later finding its reincarnation as Sitecore Forms, an integral part of Sitecore platforms since version 9. Customers enjoyed this built-in solution provided along with their primary DXP, however with the headless architecture of XM Cloud there were no CD servers any longer, therefore no suitable place for saving the collected user input. There was clearly a need for a SaaS forms solution, and this gap if finally filled out!

An interesting fact: until the release of Forms with XM Cloud the relevant composable solution for interacting with the visitors was Sitecore Send, and because of that Sitecore logically decided to derive XM Cloud Form module from Sitecore Send codebase (as it already had plenty of the desired features), rather than from legacy Sitecore Forms.

Sitecore XM Cloud Forms

So, what we’ve got?

The main goal was to release a new Forms product as SaaS solution that integrates with any custom web front-end. The actual challenge was to combine the ultimate simplicity of creating and publishing forms for the majority of marketing professionals along with tailoring this offering optimized for typical headless projects. In my opinion, despite the complexities, it was well achieved!

Let’s first take a look at its desired/expected capabilities:

  • Template Library
  • Work with Components Builder
  • Use external datasources for pre-populating form
  • Reporting and analytics
  • Ability to create multi-step and multi-page forms
  • Conditional logic (not available yet)

One would ask, if there’s no CD server or any managed backend at all, where does the submission go into? There might be some SaaS-provided storage along with the required interface to manage the collected input. Incorrect! There’s none. It was actually a smart move by Sitecore developers who decided to kill two birds with one stone: save effort for not building a universal UI/UX with the tool that will hardly satisfy variable needs from such a diverse range of customers/industries, that would be hardly doable. But the second reason is more legit: avoid storing any Personally Identifiable Information data, so that it won’t be processed within XM Cloud, leaving particular implementation decisions to leave to customers’ discretion.

That is done in a very composable SaaS manner, offering you to configure a webhook, passing a payload of collected data to the desired system of your choice.

Webhooks

Upon the form submission, the webhook is triggered to submit the gathered data to the configured system, it could be a database, CRM, CDP, or whichever backend is for some form. Even more, you can have shared webhooks so that multiple forms can use the same configured webhook. Similarly to the legacy forms that submitted their data into xDB, the most logical choice would be using powerful Sitecore CDP for processing this data. However with webhooks, the use case of XM Cloud forms becomes truly universal, and if you combine it with Sitecore Connect – that could span to whichever integration gets provided by it.
Webhooks come with multiple authentication options, covering any potential backend requirement.

Let’s see it in action!

The editor looks and feels like XM Cloud Pages – similar styling, layout, and UI elements:
02 03

First, let’s pick up the layout by simply dragging and dropping it on a canvas. For simplicity, I selected Full Width Layout. Once there, you can start dropping fields to a chosen layout:

04
Available Fields:
  • Action Button
  • Email
  • Phone
  • Short Text
  • Long Text
  • Select (single dropdown)
  • Multi Select (where you can define the number of options, say selected 3 of 9)
  • Date
  • Number
  • Radio
  • Checkbox (single)
  • Checkbox Group
  • Terms & Conditions
  • reCAPTCHA
Besides input-capturing elements, you can also define “passive” UI elements from underneath Basic expander:
  • Text
  • Spacer
  • Social Media – a set of clickable buttons to socials you define
  • Image, which has a pretty strong set of source options:
Media Upload options
Look at the variety of configuration options on the right panel. You can define:
  • Background Color within that field – transparent is the default one. You can even put a background image instead!
  • Settings for the field box shadows which also define Horizontal and Vertical Lengths, Blur and Spread Radiuses, and of course – the shadow color
  • Help text that is shown below and prompts some unobvious guidance you’d like a user to follow
  • For text boxes, you can set placeholder and prefill values
  • The field could be made mandatory and/or hidden by the correspondent checkboxes
  • Validation is controlled by a regex pattern and character length limit
  • Additionally, you can style pretty everything: field itself, label, placeholder, and help text, as well as set the overall padding to it

Please note, that at the current stage, the edited form is in a Draft state. Clicking Save button suggests you run your form in a Preview before saving, and that was very helpful – in my case, I left the Full Name field as a hidden field by mistake, and preview mode immediately showed me that. After fixing the visibility, I am good to go with saving.

The Forms home screen shows all the available forms. To Activate, I need to create a Webhook first, then assign it to the form. In addition, you define the action you want to do upon webhook submission – redirect to URL, display success message, or maybe – do nothing, as well as configure failure submission message.

This time Activate button works well and my form is listed as Active. From now on you cannot edit fields anymore and you cannot change the status back from Active. Therefore always verify your form in Preview before publishing.

Weirdly, you cannot even delete a form in Active status. What you can do however is a duplicate active form into a draft one, and you could go on with fields editing from there.

Forms listing

Testing Form

The most obvious desire at this stage is to real-test your form before usage. And luckily developers took care of that as well.

Testing webhook
I also created a webhook catcher with Pipedream RequestBin. On my first go, I faced a deadlock being unable to submit the form for the test. The reason for this was that I mistakenly checked both the Hidden and Required checkboxes on a field and could bo progress from there. Even the validation message did not show on a hidden field. Another mistake was that I overlooked that on Preview and published form into the active state. Hopefully, developers will find a solution to it sooner rather than later.

I give it another try to test how validation works:

Validation in action

Once validation passes, Test Form Submission dialog shows you the JSON payload as it goes out along with HTTP headers supplied with this webhook request. Let’s hit Submit button and see the confirmation – I chose to see a confirmation message that shows up.

Webhook catcher shows all my submitted data along with HTTP headers, everything was sent and received successfully!

webhook catcher

Multipage Forms

Multipage Forms are supported and that is impressive. Pay attention to the Add Page link button in between page canvases. Once activated, it also enforces Next button on non-final page canvases that trigger switching to the next form page:
20

What’s Next? Pages Editor!

Let’s use this newly created form from XM Cloud pages. Please note a new section called Forms under the Components tab. That is where all of your active forms reside. You can simply drag-drop this form to a desired placeholder, as you normally do in the Pages editor.

Consume Forms from Pages Editor

Please note: you must have your site deployed to an editing host running on Headless (JSS) SDK version 21.6 or newer to make it work – that is when XM Cloud Forms support was added. In other case, you face this error:

BYOC error before SDK 21.6

Experience Editor and Components Builder

Surprisingly, created forms are available from Components Builder:
Forms in Components Builder
However, Experience Editor does not have a direct way of consuming XM Cloud Forms. I tried the following chain of steps in order to make it work:
  1. Create and Activate a new form from Forms editor
  2. Consume it from the Components builder into a new component using BYOC, then publish this component
  3. Open Pages app, find the component with an embedded form you’ve built at step (2) and drop it to a page, then publish
  4. Open that same page in Experience Editor

Live Demo in Action

As you know, often a video is worth a thousand words, so here it is below. I’ve recorded the whole walkthrough from explaining to showcasing it all in action up to еhe most extreme example – when you create and publish a form, then consume it from the XM Cloud Components builder, making the part of a composite component, which in turn is used Pages editor to put down on a page which also opens up successfully in the Experience Editor. Unbelievable, and it all functions well. Just take a look yourself:

Developers Experience

As developers, how would we integrate forms into our “head” applications? That should work with a Forms BYOC component for your Next.js App coming out of the box with SDK. I spotted some traces of XM Cloud forms a while ago as a part of Headless JSS SDK 21.6.0 a while ago when it was in a state of “Canary” build. Now it got released and among the features, one can see an import of SitecoreForm component into the sample next.js app, as part of pull request merged into this release.

The documentation is available here, but … everything is so absolutely intuitive, that you hardly need one, don’t you?

Template Library

It is worth mentioning that XM Cloud Forms contains a Template Library handful of pre-configured forms you can use straight away or slightly modify as per your needs. There is an expectation it will grow with time covering any potential scenario one could ever have.
Template Library

Licensing

Since Forms are bundled into XM Cloud they’re included with every XM Cloud subscription.

What is missing?

  • file upload feature is not supported – webhooks alone are not sufficient to handle it
  • ability for customization and extension – hopefully, it comes as there’s an empty section for custom fields

Hopefully, the product developers will implement these and more features in the upcoming releases. But even with what was released today, I really enjoyed XM Cloud Forms builder!

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.