XM Cloud Developer Instructor Led Training (ILT)

Thanks to the Sitecore MVP program I recently attended a 2-day, instructor led, online XM Cloud training course “Sitecore XM Cloud Developer Fundamentals ILT”. This post is intended to provide some details about the course content and structure so that potential purchasers can make an informed assessment of the applicability of the course to their training needs. It is not intended to be a course evaluation or review.

Delivery mode

The course is held over two days and is available in a number of time zones. It is led by a Sitecore Learning trainer and is delivered via a combination of:

  • an MS Teams video conference session with instructor slides,
  • an “in-browser” virtual machine for hands-on development work (i.e. not RDP),
  • Hands-on Lab contents detailed in the Sitecore Learning portal.

The slides are available for review in the Learning portal but are not available for download. The session is not recorded. The course does not cover using .NET as your headless rendering technology, it is more directed towards React/Next.js (although that aspect is only in the latter part of Day 2.)

Course overview

The two days are split as follows:

  • Day 1: Cloud Administrator
  • Day 2: Local Development

Cloud Administrator (Day 1) focuses on an introduction to XM Cloud concepts, the Sitecore Cloud Portal and XM Cloud Deploy App, the new Pages editor, and numerous typical Sitecore concepts such as templates, items, fields, standard values, presentation details, renderings, rendering parameters, workflow, and security. Hands-on labs are carried out using an XM Cloud instance deployed via the Portal which has access to Pages, Content Editor, and the new Explorer.

Local Development (Day 2) is more targeted towards hands-on development activities and covers quite a range of topics such as spinning up a local container environment, the XM Cloud Starter Template, serialisation (SCS) and the CLI, the Layout Service, creating renderings and components, and numerous other topics including an introduction to the Next.js SDK. The second half of the day is quite dense with content. This is all conducted in a VM provided by Sitecore Learning.

Note: The browser-based virtual machine for Day 2 uses a service called OrasiLabs. You might want to ensure that your corporate network environment will allow access to it (thanks for the suggestion George Tucker)

Course contents are outlined in more detail in the following section.

Course contents

Day 1: Cloud Administrator

Module 1 Intro to XMC

  • Traditional vs Composable DXP Intro to WCMS and DXP
  • XM Cloud architecture overview. What is XM Cloud, why use it, architecture overview
  • Sitecore portal and XM Cloud Deploy App. Navigating the portal and Deploy app, Terminology (Experience edge, rendering host, editing host)
    LAB: create a project
  • Managing sites, projects, environments. Anatomy of a site, intro to demo projects
    LAB: create a website

Module 2 XM Cloud Pages

  • Overview and using the XM Cloud Pages app
  • Common fields, placeholders, assigning components
  • Versioning and multi-language, presentation and shared layouts
  • LABS (covers the above)

Module 3 Data Modelling

  • Content Editor
  • Items, Content vs “Definition items”
  • Templates, fields, grouping via field sections, icons, template inheritance (standard template and recommended practices), field versioning.
  • Standard values, insert options, tokens.
  • Quick Helix overview
  • LABS (covers the above topics apart from Helix)

Module 4 Presentation

  • Layouts and presentation details, page and partial designs, datasources.
  • XM Cloud Components, grids, breakpoints.
  • A peek at the XMCloud Component Builder and comparison with Headless SXA components
  • LABS: page and partial designs, component properties

Module 5 Workflow and Webhooks

  • Sitecore workflows, states/commands/actions, creating and assigning.
  • Using webhooks in workflows
  • LAB

Module 6 Security

  • Users, roles, built in roles, Access Viewer, Security Editor.
  • LAB

Day 2: Local Development

Module 1 Local Development

  • Accessing the VM.
  • Cloning the XM Cloud Starter Template
  • Initializing and upping containers for local Sitecore XM Cloud dev
  • XM Cloud Topology
  • LAB: Spin up XM Cloud local dev environment

Module 2 Serialization

  • Sitecore Content Serialization, config files, modules, environments, rules.
  • CLI (login, connect, serialize between environments, packages, IAR)
  • LABS: connecting and using SCS

Module 3 Creating Components

  • Intro to Layout Service.
  • XM Cloud and related concepts: Experience Edge, Edge GraphQL, Headless Services, Edge Connector
  • Routing in headless Sitecore+Next.js paradigm, wildcard/catch-all routes
  • Partial design inheritance.
  • Rendering definitions, rendering parameters and rendering parameter templates, Toolbox.
  • Correspondence between Sitecore renderings and React component implementation.
  • SXA and Headless SXA base rendering parameter templates and reasons/usage for these.
  • LABS: Partial design inheritance, assigning page designs to templates and assigning partials, rendering param template. Creating a Sitecore rendering and corresponding React component

Module 4 Rendering Fields

  • Fields and field types. Route fields, complex field types, datasource fields, layout service API response JSON.
  • Rendering route fields and datasources in React code.
  • Datasource location and datasource template rendering options. Local datasources.
  • withSitecoreContext, useSitecoreContext, withDatasourceCheck
  • Rendering parameters and rendering variants in React code.
  • Cloning existing renderings via SXA script.
  • Oout-of-the-box Sitecore Headless modules.
  • LABS: Render route fields, complex field types, rendering datasource. Rendering variant, clone rendering.

Conclusion

Hopefully the above will give decision makers enough information to determine which developers are best suited to attend the course and its relevance to project requirements. The course is available here (login required)

Using GraphQL with XM+EDGE in the Sitecore Demo Portal

Setting up an XM+EDGE instance in the Sitecore Demo Portal, I encountered a couple of gotchas with querying data via the Experience Edge GraphQL IDE. If you are already familiar with the Portal but are getting the error:

The field 'item' does not exist on the type 'query'

then skip down a couple of paragraphs to the solution.

tl;dr

  • Create a publishing target with database “experienceedge
  • Check the Publish to Experience Edge checkbox and save the new target
  • Publish the site to your new Edge publishing target and wait a few seconds before querying it, while the schema gets generated
  • PROFIT

The Demo Portal

The Sitecore Demo Portal was created to provide the ability to easily spin up demo sites that you can play with, learn, and showcase. You can very quickly create a complete headless site or commerce instance, as well as an empty XM + Experience Edge instance:


It’s available to Sitecore partners, MVPs, and Sitecore employees, and has been covered a few months back by Jeremy Davis and others, so I won’t go into any detail here, but you can learn more from Jeremy’s blog post or Neil Killen’s blog post.

I was looking to learn more about Experience Edge (and in particular use GraphQL on Edge) as I haven’t had the opportunity to work on a new Edge site for a customer, so I logged in and set up an XM+EDGE demo instance. This deployed surprisingly fast – in a matter of seconds – the team have really supercharged the deployment process since I last tried it a while back.

NOTE: This is NOT XM Cloud, it is a demo instance of XM that uses Experience Edge.

The problem

Once I navigated to the Experience Edge URL at https://edge.sitecorecloud.io/api/graphql/ide and configured the HTTP header for my API key, I tried to run a simple query to retrieve the default Home item:

query{ 
	item(path:"/sitecore/content/home", language:"en"){
    id
  }
}

The result was the following error:

{
  "errors": [
    {
      "message": "The field `item` does not exist on the type `query`.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ]
    }
  ]
}

The solution

The cause of the error was that I had not yet published my site to Edge, so the schema was not available to query. To fix this, do the following:

Create a new publishing target with Target Database “experienceedge” and check the Publish to Experience Edge checkbox:


Publish your site to that publishing target


Wait until it finishes, refresh your IDE GraphQL IDE window, and wait a few seconds for the schema to be generated, then run your query


More info

https://doc.sitecore.com/xp/en/developers/hd/201/sitecore-headless-development/install-and-configure-the-experience-edge-connector.html

https://doc.sitecore.com/xp/en/developers/hd/201/sitecore-headless-development/configure-publishing-targets.html

Slides from my SUGCON ANZ 2022 presentation

This year I had the very good fortune to attend and present at SUGCON ANZ in Melbourne – the first ANZ SUGCON since 2019 in Sydney. It was fantastic to hang out with some long lost Sitecore friends that I hadn’t seen since before COVID times, and to meet new Sitecore community folks as well as the crew from Sitecore – some of whom had travelled a long way to get there.

This year my presentation was “Making the journey to headless without losing your head” which looks at some of the challenges and choices that you might want to consider when taking your first steps towards a Sitecore headless implementation, based on my experiences over the last couple of years doing headless development with Sitecore XP and JSS.

Anyway, for what it’s worth here are my slides. There is some supporting content in the speaker notes, if you get that far… Thanks to all who attended, especially the Perth Sitecore crew, and thanks again to the organisers of SUGCON for the opportunity and good times.

Using other GraphQL types

Recently I needed to expose a GraphQL query that accepted a list of Sitecore item IDs and returned the item data for those IDs. I wanted to use the Content Search API for efficiency reasons and because the items might not all be colocated in a folder.

Out of the box, the Sitecore GraphQL Search query does not allow you to do this, but a look into the GraphQL assembly revealed that the ListGraphType might do the job.

The resulting extensions to Sitecore.Services.GraphQL.Content.Queries.SearchQuery looked like this:

public class MySearchQuery : Sitecore.Services.GraphQL.Content.Queries.SearchQuery
    {
        public MySearchQuery()
        {
            Arguments.Add(new QueryArgument<ListGraphType<StringGraphType>>()
            {
                Name = "itemIds",
                Description = "Filter by Sitecore item IDs"
            });
        }

        //override the ContentSearchResults method here...
}

The GraphQL query was then able to pass in an array of item IDs which looked something like this:

query ExtendedSearch {
	searchItemsById(itemIds:["6a6f0d75-2a1d-4f70-b639-622d308d9e11","{EF1848D8-6C43-4E52-A04F-287A12E19D12}"]) {
      results {
      items {
        item {
          id
          field(name:"myItemField"){
            value
          }
        }
      }
    }
  }
}

One thing to note is that, although you are passing in a Guid like “6a6f0d75-2a1d-4f70-b639-622d308d9e11”, the Sitecore GraphQL content search results will come back with a value like “6A6F0D752A1D4F70B639622D308D9E11”.

GraphQL and Sitecore – video series Part 1

Today I released Part 1 of a video series on using GraphQL with Sitecore. In this first part, I cover getting up and running with GraphQL through:

  • installing Sitecore’s JavaScript Services Server components
  • setting up an endpoint configuration
  • creating and using an API key, and
  • running a query in the UI.

In later videos we’ll dig deeper into configuration, item and search queries, fragments, integrated GraphQL, and extending and customising GraphQL in the context of Sitecore.

Hope that people find it useful.

Redirects and the Layout Service

In a typical Sitecore ASP.NET server-side rendered solution, redirects are handled by returning a 301 or 302 HTTP status code to the client browser to signal that the client should redirect to a particular URL.

In a Sitecore Headless solution, things might not be quite so simple. For one thing, the Layout Service does not readily provide a way to return a 301/302 redirect status code in the HTTP response from the API. In addition, even if you convinced the Layout Service to return a 301, if you were using the JSS SDK in a JavaScript application running on the client browser, in-app routing might need to handle redirects returned from the API call and identify which route to use and redirect accordingly (in the app itself, not with a full page reload.) Turns out that isn’t as simple as it might appear either.

Both kinds of music – country AND western!

There are at least two distinct types of redirect that we might want to handle in our headless Sitecore application:

  • Redirects when no item or route exists in Sitecore that matches the requested item
  • Redirects when an item exists and its context resolves, but we want it to redirect elsewhere

Redirects where no item exists (these aren’t the items you are looking for)

This is the typical “vanity URL” scenario. For example we might have published a nice, friendly URL for potential customers such as https://mysite.com/newmodels which will redirect to https://mysite.com/catalogues/cars/2021-models. It’s easier to remember and easier for the user to digest. It might also have SEO benefits to use the first URL rather than the second one.

Sure, we can handle this using a CDN or via web server modules like URL Rewrite, but that isn’t very helpful to our digital producers who want to manage redirects themselves in the CMS and publish the new vanity URL alongside a marketing campaign.

This is where modules like the 301 Redirect Module or SXA Redirects come in. Both of these empower your users to manage redirects in the Sitecore tree, but they were not designed to work with Sitecore Headless Services. That said, you could leverage either option, or roll your own, and patch it in. I’ve outlined how you might do that later in this post.

Redirects where an item exists (I resolve therefore I am)

In this scenario we might have a node in the Sitecore content tree that has no content but has descendants that do. Perhaps a Category page that holds all items in a particular category, but that has no actual content itself.

Now you might argue that we shouldn’t have nodes in our IA that have no useful content – and I’d be on your side in that argument! – however some clients would appear to disagree, and so here we are.

Digging into the Layout Service code

Let’s look at each scenario and see how they might be achieved in the Layout Service, as well as some pointers on what to avoid.

Redirects with no Sitecore items

As mentioned earlier, patching into the usual MVC pipelines is no use with the Layout Service because it just blows right past those suckers and does its own thing in a Controller API. The controller that runs the show is in:

Sitecore.LayoutService.Mvc.Controllers.LayoutServiceController

It inherits from System.Web.Mvc.Controller and exposes the “placeholder” and “render” actions in the Layout Service API via MVC controller action methods. The first thing that I looked at was overriding the virtual methods, but after going down a rabbit hole it became apparent that, although overriding the methods would be simple enough, setting up the constructor was more problematic and needed quite a bit of hackery which would prove hard to maintain and upgrade in the future. I then looked into patching the pipeline.

Finding the right place to patch was the key to doing this. Long story short, the place to patch is in the <mvc.requestBegin> pipeline, just after Sitecore.LayoutService.Mvc.Pipelines.RequestBegin.ContextItemResolver like so:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/&quot; xmlns:set="http://www.sitecore.net/xmlconfig/set/"&gt;
<sitecore>
<pipelines>
<mvc.requestBegin>
<processor type="Mysite.Feature.Redirects.Pipelines.RedirectHandler, Mysite.Feature.Redirects" resolve="true"
patch:after="*[@type='Sitecore.LayoutService.Mvc.Pipelines.RequestBegin.ContextItemResolver, Sitecore.LayoutService.Mvc']"/>
</mvc.requestBegin>
</pipelines>
</sitecore>
</configuration>

Then in your RedirectHandler you can add whatever approach floats your boat to identify and process redirects, returning the appropriate 301/302 status code and URL.

However….

As an Australian politician and Prime Minister once said, “Life wasn’t meant to be easy”. In this case there’s a couple of gotchas.

In a JSS application, a client browser requests the initial app from the server and on subsequent requests calls back to the Layout Service API for the route data. This means that there are actually two scenarios to consider: the initial request to the server and the subsequent calls to the API. How you handle this is dependent on your topology and your choice of SDK.

For example, in an SSR setup with a Node.js proxy, your proxy app will need to be able to handle redirects both from the initial request, and on subsequent proxied API requests. In a CSR setup this will be handled by your JSS app. In either case, your JSS app will need to be able to handle HTTP responses with redirect status codes.

Working on this with my front end colleague in an SSR topology using the React JSS SDK, we encountered issues with Axios following 301s without any way of trapping it. It was fine for the initial request, but we were unable to handle Layout Service API calls that returned 301 in the React app. Your mileage may vary, and if you are using .NET rendering host then this should not be an issue.

Another gotcha is CORS. The LayoutServiceController has a bunch of ActionFilterAttributes, one of which is EnableApiKeyCors. This adds some CORS magic to the response and you might need to add this code to the response your redirect handler, depending on your scenario. For reference, you might add something like this to your response code:

HttpResponseBase response = filterContext.HttpContext.Response;
response.Headers["Access-Control-Allow-Origin"] = header;
if (string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Allow-Headers")))
response.AddHeader("Access-Control-Allow-Headers", "*");
if (string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Allow-Credentials")))
response.AddHeader("Access-Control-Allow-Credentials", "true");
if (string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Allow-Credentials")))
response.AddHeader("Access-Control-Expose-Headers", "ETag");
if (!string.IsNullOrWhiteSpace(response.Headers.Get("Access-Control-Max-Age")))
return;
response.AddHeader("Access-Control-Max-Age", "10");
view raw CorsHeaders.cs hosted with ❤ by GitHub

Phew. This post is longer than I had intended. Nearly there.

Redirects where an item exists

This one is a bit easier. Option one is to have a fight with the customer and convince them to just put some content on the “node with no content”.

If that fails, you could create a custom item type in Sitecore to handle the redirection. I created a NavigationRedirectNode template that had a Treelist field from which you could pick an item from the site tree. Then in the code I just checked for that template ID and returned the redirect. In my case, because of the Axios issues mentioned above, we opted to return the route to which we wanted to redirect via the ItemContext (similar to this post) and handled that in the app. I’m not saying this is the only or best way – it’s just where we ended up.

However! There was a gotcha. The item has to have layout. It doesn’t have to have anything on it, but if you don’t have a Sitecore layout applied to the item then Sitecore jumps in, hijacks the request, and you end up at “The layout for the requested document was not found” with a red screen of doom.

Summary

As you can see, there’s a bit more to redirects when using Sitecore Headless Services compared to a “traditional” Sitecore MVC solution. The above represents my own experiences with applying redirects in a JSS React SSR solution, and is by no means comprehensive, but I hope that it provides some pointers on your own headless travels 🙂