Exploring Sitecore Headless Services

A while ago I wanted to test the possibilities with Sitecore Headless and in the mean time also learn a new platform and even language.
I decided to build a simple mobile app based on content coming from Sitecore’s Headless Services APIs.
In this and following blog posts I want to share the knowledge I gathered building a simple Flutter Mobile App using content and tracking functionalities from Sitecore XP.

In this post I will take a look at what Sitecore Headless Services has to offer us in terms of endpoints and functionalities, looking at how we could use these to build an actual Mobile App.

Architecture

Many know of JSS and the fairly new ASP.NET Core Rendering Host, but those frameworks could be considered as just examples. Both frameworks are built on top of the same APIs from Sitecore’s Headless Services.
To fully understand the possibilities, let’s see which APIs we can utilize to build our headless platform.

In above diagram we can see that Sitecore has multiple connection points, all being

  1. Layout Service; an API that can be used to get content and page structure information.
  2. GraphQL; GraphQL based APIs you can use to get fetch all details from any Sitecore item, including search capabilities.
  3. Tracking APIs; an API which can track and trigger goals, events, outcomes and page views.
  4. xConnect; Sitecore’s abstraction layer around the xDB, which also offers OData APIs to create, read and update user interactions.
  5. Custom; There will always be a need for custom APIs as not all of Sitecore’s Experience Platform capabilities are reachable through APIs and there is of course always a level of client specific custom functionalities we need to build in a Sitecore platform.

Let’s see which of these endpoints we can use for different purposes, serving content and tracking a user’s behavior.

Content

The first thing we want to do, is show content in our App. There are two possible integrations we can build to make this happen, using the Layout Service or using GraphQL.
The Layout Service response is meant to return a JSON response object containing a full structure of how a page should be organized, which components are used and in which placeholders they are contributed.
The GraphQL API on the other side does not return this structured JSON, instead it just returns fields from Sitecore items in the way you configure it. For this reason, building an App based on the Layout Service would be the logical thing to do.
This does not mean the GraphQL API is useless. We can use GraphQL for serving other, structured kind of content, like a dictionary.

The Sitecore JSS site has some pretty detailed documentation about the inner workings of the Layout Service: https://jss.sitecore.com/docs/fundamentals/services/layout-service
In our case we want to focus on doing a request to the Layout Service and then using its response to build our App layout.
The API is (by default) using the following URL: /sitecore/api/layout/render/jss
This API requires a couple query string parameters (the following is taken from the JSS documentation)
/sitecore/api/layout/render/jss?item=[path]&sc_lang=[language]&sc_apikey=[key]

ParameterDescription
itemThe path to the item, relative to the context site’s home item or item GUID
sc_langThe language version
sc_apikeyAn SSC API Key required to perform Layout Service requests.
sc_siteThe name of the context site.

If we want to do a request to the homepage, the URL would look something like this:
/sitecore/api/layout/render/jss?item=/&sc_lang=en&sc_apikey={00000000-0000-0000-0000-000000000000}

The response is a JSON object with one top-level object, “sitecore”, and in that two other objects, “context” and “route”.

{
  "sitecore": {
    "context": {
	 …
    },
    "route": {
	 …
    }
  }
}

The context object contains some contextual properties, like the requested page, site name and language.
The route object is the one we can use to build our page, it contains a multi-level structure of all things related to the page Sitecore item and the Final Layout field converted to JSON instead of XML.
The Route object contains a list of placeholders as defined in Sitecore and in the Placeholder objects we can found our different renderings/components with all their fields and optionally extra placeholders.

{
    "route": {
        "name": "Home",
        "displayName": "Home",
        "fields": {
            "NavigationTitle": {
                "value": "Home"
            }
        },
        "databaseName": "web",
        "deviceId": "fe5d7fdf-89c0-4d99-9aa3-b5fbd009c9f3",
        "itemId": "6ef11188-4b00-443c-8828-4ed3a68be665",
        "itemLanguage": "en",
        "itemVersion": 1,
        "layoutId": "2218ad4f-87c8-4686-9c44-d36d509562d6",
        "templateId": "49588140-2f59-4f56-ae5a-e3fb922ac29f",
        "templateName": "Home Page",
        "placeholders": {
            "header": [
                {
                    "uid": "7d4f689f-208c-4ea3-88ba-0bc6e615771e",
                    "componentName": "Header",
                    "dataSource": "",
                    "params": {},
                    "fields": {
                        "navItems": [
                            {
                                "url": "/en/",
                                "isActive": true,
                                "title": "Home"
                            },
                            {
                                "url": "/en/Products",
                                "isActive": false,
                                "title": "Products"
                            },
                            {
                                "url": "/en/Services",
                                "isActive": false,
                                "title": "Services"
                            }
                        ]
                    }
                }
            ],
            "main": [
                {
                    "uid": "bb562955-2cf5-4a57-b6c7-ddf2278ec0e0",
                    "componentName": "HeroBanner",
                    "dataSource": "{01ffd680-39fa-4d03-9e0c-7ae0dbf05747}",
                    "params": {},
                    "fields": {
                        "Image": {
                            "value": {
                                "src": "https://placekitten.com/600/300",
                                "alt": "Example Site",
                                "width": "1920",
                                "height": "510"
                            }
                        },
                        "Subtitle": {
                            "value": "Lorem Ipsum Dolor Sit Amet"
                        },
                        "Title": {
                            "value": "Example Site"
                        }
                    }
                }
            ],
            "footer": [
                {
                    "uid": "66b647e6-3d33-45ef-af06-ebb2175bc56b",
                    "componentName": "Footer",
                    "dataSource": "",
                    "params": {},
                    "fields": {
                        "footerText": "Copyright"
                    }
                }
            ]
        }
    }
}

Personalization

One of the biggest reasons for Sitecore’s clients to choose Sitecore is, or has been, the xDB capabilities to personalize and optimize a user’s experience on their website.
Enabling the content editor to keep using these functionalities in a Sitecore Headless scenario might seem a little problematic, because page are no longer directly being served by the CD role. However this does not mean that it doesn’t work anymore.

The Layout Service is capable of personalizing and A/B testing renderings and pages like we have been used to in the past. Personalization rules still take affect when the Layout Service determines the output of the Route object, returning different renderings or data sources as needed.
Pageview tracking also still works. Instead of CD servers tracking these visits, the Layout Service is capable of tracking the API requests as pageviews (which can be enabled/disabled).
Apart from the page visits, we probably also want to track some goals. As pageview tracking works, tracking page goals will also work, but sometimes we want to track goals, page events and outcomes in a different way. For this purpose Sitecore Headless Services also has a Tracking API.
The JSS documentation has a page explaining how you can enable and use it in a JSS App (https://jss.sitecore.com/docs/fundamentals/services/tracking), but how to use it in different scenarios is still a bit vague after reading it.

So far I have found that there are two APIs we can use:

  1. An event API at /sitecore/api/jss/track/event?sc_apikey=[key]
  2. An API to flush the user’s session at /sitecore/api/jss/track/flush?sc_apikey=[key]

To track an goal we can use that first API to send a JSON object in a POST request.
The JSON object looks something like this:

{
	"goalId": "[goal ID]"
}

The JSON object determines what kind of event you trigger, here are some examples:

Goal

{
	"goalId": "[goal ID]"
}

Outcome

{
	"outcomeId": "[outcome ID]",
	"currencyCode": "[currency code]",
	"monetaryValue": "[monetary value]"
}

Page Event

{
	"pageId": "[page ID]",
	"url": "[url]"
}

Campaign

{
	"campaignId": "[campaign ID]"
}

Cookies

For tracking to work, cookies are used and need to be stored. This is the same approach as used in an MVC based Sitecore platform, where a session cookie is stored in a user’s browser to identify the user. In the session object itself the page visits, goals, etc. are being stored until they are pushed to xDB.

This is however something which won’t work out-of-the-box in a headless scenario unless it is the browser doing the Layout Service requests.
So you need to make sure to store the cookies somewhere in your App’s session storage or direct it back to the browser in case of a browser based application, like the Rendering Host does.

In the next post we will take a look at how we could utilize these APIs and features and start building a Flutter App.