Friday, March 18, 2022

Sitecore Managed Cloud with SXA and JSS

 Sitecore Managed Cloud with JSS and NextJS

First of all: thanks to David Szöke who recently also wrote an article on Sitecore Managed Cloud saving me a lot of time as I now have to write a lot less - thanks for sharing David! 👍
And if you haven't read it yet, you should.

Before we continue, one small step back: if you don't know what Managed Cloud is and you need to get acquainted the documentation is (like always) a good start. 
Sitecore Managed Cloud is a hosting service that enables customers to deploy Sitecore Experience Manager (XM), Sitecore Experience Platform (XP), and Sitecore Experience Commerce (XC) on Microsoft Azure. It is an alternative to running the Sitecore platform on premises or via the public cloud. With Sitecore Managed Cloud, Sitecore actively monitors, manages, and maintains both the infrastructure and the applications.


So now you know everything about Managed Cloud. You might want to learn a bit about Kubernetes and using kubectl as well, but that is not the focus here. I would like to share some more experiences (and issues) we recently had in our project with SXA, JSS and NextJS that we are hosting on this Managed Cloud. Combined with the documentation and the article from David that should get you going.


Installing JSS - SXA - Horizon

In our project we are using SXA (with SPE), JSS and Horizon. In Managed Cloud these are not installed for you, but it is well documented on how to do this. You will need to make changes to json and yaml files in the Application repository - one tip: get enough sleep so you are focused.. a typo is easily made (and not always easy to find.. ). Also note space matters, and tab is no space.

Although following some documentation seems very simple, we did run into some issues. There is a mechanism that will make sure the init tasks don't override stuff that was already initialized and this blocked the installation of SXA because the Solr indexes could not be created. We contacted Sitecore Support and got a fix for the issue. A new start script was provided for our solr-init container. We added it to the solution and with a small adjustment to the DockerFile we added it into a new image that could be used to execute the installation.
ARG PARENT_IMAGE
ARG SXA_IMAGE

FROM ${SXA_IMAGE} as sxa
FROM ${PARENT_IMAGE}

COPY --from=sxa C:\module\solr\cores-sxa.json C:\data\cores-sxa.json

COPY Start.ps1 c:\

Now we were all set and ready to go. The rendering host was added and the application seemed to work. Except...

JSS sc_apikey

We have some components that need to request data from the Sitecore GraphQL endpoint at the client (e.g. to load "more" data in a list). This didn't work - the same queries did work in the rendering. Luckily the error was pretty clear and said that the sc_apikey was not provided. Which was weird because we could see the key in the header of the request. It also worked perfectly on our local containers so we assumed the issue was related to the infrastructure. 

As a workaround Sitecore Support suggested to include the sc_apikey in the querystring instead. 

We were already using a custom graphql client factory to build the url so that was a rather easy task. We did build this factory also because we already encountered issues with the needed environment variables and their availability on clients/servers, differences between local setups and development environments in the cloud and mainly because the url for the GraphQL endpoint on the rendering host is the Content Delivery but on the client it has to be your rendering host (forwarding it to the CD). Just a tip that such a factory might be a good idea if you also need to use the sc_apikey on the client.

The querystring solution works. And the header issue was related to nginx blocking headers with underscores. There is a fix available through Support though - so you can go back to using the header instead of the querystring.


JSS editing secret

When you deploy to the Managed Cloud you should use the KeyVault that is provided for you to store all kind of secrets and environment variables. Lots of them are already in there - provided by Sitecore. But you will need to add some yourself if you are using additional modules like JSS. One of those secrets is the JSS editing secret. This has to be added to the Content Management (CM) and the rendering host. A good developer is by default lazy and tends to start copy/pasting...  but in this case that was not such a good idea as apparently in the CM Sitecore called the parameter SITECORE_JSS_EDITING_SECRET and in the rendering host it has to be called JSS_EDITING_SECRET.  Just a small difference but it does break the editors and it can take some time to find out why. But now you are warned and shouldn't make that same mistake :)


Kibana

This might sound like a sunny Brazilian beach but it's actually the place to find your logs - based on Elastic Search. You'll find the information in the Sitecore docs, your url and login information in the secrets and you're good to go. 

If you are not used to this environment it can be a bit tricky to find what you are looking for. First of all: find the navigation - and go to "Discover" in the "Analytics section.  
You should get a lot of entries that might make no sense so you need to start filtering. The servicename is a good place to start: those will be like "sitecore-xm1-cm" and so on. The pod_name can also be useful if you know it. Best way for me was start simple and check the results to see which fields are available and can be used to filter further to get the logs needed.



Conclusion

Don't be afraid of the Managed Cloud. I hope I saved you some time with this blog. Read Davids article as well. Call Support when needed. And enjoy the result when it's all working.


Monday, February 7, 2022

My first JSS story

 My First JSS story: the good, the bad and the undocumented


I am what you might call a typical "Sitecore developer" - started working with Sitecore when we used to create webforms, then moved to MVC based sites, then we added some SXA and PAAS.. things changed. And as you probably know, things are changing constantly and lately in a rapid pace. A few weeks ago I also started my first headless adventure. To make it a bit more exciting the project would use the -at that moment- brand new Sitecore 10.2 with JSS and Next.JS hosted with Sitecore Managed Cloud and using containers on Docker for local development. We are also integrating with a non-Sitecore DAM and Commerce solution (both headless and SAAS) but that is .. well, out-of-scope here.
Now, as I actually don't know any of these technologies except for Sitecore that would be exciting indeed. It's been ages since I wrote some Javascript, but that has become Typescrypt and React and Next and so on now. Docker is a black box and the rest is even more unknown. If this is all very familiar to you, you can stop reading. But I do assume that is not the case for everyone.

Anyway, as I'm probably not the only adventurer our there I'd though I would share my findings and maybe it can help someone in a similar situation. This is the first post - more will probably follow. I'd like to focus on the setup in this one as that is the first step in a project.

Setting up Sitecore 10.2 with JSS and Next.JS in containers

I'm afraid I can't give you an easy setup guide or simple steps to follow for what I did as it was a bit trial and error but in the end that might have been the best way to learn a bit about what I was actually doing. What I can do is give you some tips, links to the documentation pages you'll need and positive vibes to help you do the setup as well.

But first: our requirement. We want a Sitecore 10.2 with JSS and Next.js on a local setup in containers, and it should include Horizon and SXA (we want multisite options later, so people who should know advised us to add SXA - and as that is something I know I don't mind at all).

Sitecore Containers template for Next.js

Luckily -for a lot of us- Sitecore provides a template to start with. You can find the whole walkthrough in the Sitecore docs and after a few steps you can have an up and running Sitecore environment with a rendering host. The prerequisites are pretty straight forward and most developers will already have them installed. When performing the steps in the walkthrough:
  • Make sure your Docker is setup correctly and running on Windows containers - it is mentioned but I noticed people still tend to forget
  • Stop all processes you don't need - IIS and Solr are mentioned, you might also have some local Sitecore jobs running...
  • When creating the solution in step 4, think about the parameters
    • fetchWith: I have used GraphQL here which I think is the best option at the moment as Sitecore pushes this as your best plan to be future proof - we'll get back to this later
    • prerender: I skipped this one to get the default SSG (static site generation)
Note that you get a script called "up" to start your environment. You can adapt this script - or create a whole new one - if needed. You could for example add "iisreset -stop". This is the script to get you started every morning and I noticed it is a good practice to end the day with a "docker-compose down" which is the command to bring down all your containers again gracefully.

Docker 

Docker.. containers..  this is the point where you should at least check some cheat sheet(s). Sitecore provides one for you and you can find lots more on the internet. If you get stuck or need to do something with a container, google is your friend and StackExchange usually the answer. But it is very useful to understand at least a bit about images, containers and some commands that are quite common.


Adding modules: SXA - Horizon

Next step: adding SXA & Horizon. Two modules from Sitecore that we want installed. 

While installing modules in a containerized setup you will learn to handle docker build and compose files. This is good, as you might want/need to customize more for your specific solution. To install modules you can find documentation on a few locations, and unfortunately not all are correct. Also make sure you are looking at documentation for version 10.2 - some things changed since that version so any old docs are not always valid anymore.

If you are reading the documentation from Sitecore on containers you are likely to first find the page on adding Sitecore modules. It uses SXA as an example, but this page is wrong. The general information in still valid but the examples are not.  Luckily there is also another page in the Sitecore Docker references that has does include the correct code. From this page you'll need to add everything for:
  • Sitecore Powershell Extensions (this is needed for SXA)
  • SXA
  • Horizon
And yes, SXA does not need changes to the database anymore - that is correct.

After making these changes we run our start script again which will build the containers and spin them up.

Ok, so far so good. We have installed the modules but the site that was created earlier is of course not an SXA site. 

Importing the app

First of all we are going to create a headless tenant and site. This is straight forward and very easy - especially if you have some SXA knowledge. But then we have to move our JSS app into SXA and that is more on gut feeling actually. There is documentation on moving a JSS app into SXA but that was completely not relevant. So I actually started moving items into the SXA site and changing templates to from the JSS ones to the headless-SXA ones.

I did find some documentation on 10.1 that was a bit relevant on using getting started templates with SXA. I did not follow the steps exactly - in fact, I just picked the steps that made any sense:
  • You do need to remove the site (and app) configuration in your code, but I did that in the end
  • The part about creation your solution makes you change quite some items in Sitecore, and not all of them are actually present. Don't worry - just do the steps that can be done. The documentation is for version 10.1, and I didn't find this for 10.2 yet. Also, if you configure the settings for your app (like the "Server side rendering engine endpoint URL") make sure to use the values that are defined in the config that was created for you in your solution - do not take the values from the article.
This was a bit a trial and error step but in the end the site worked (again) so we managed. 

Issues

We bumped into quite some issues still to get our first pages working.. to a point where I as really getting ****** with all this headless **** that was not making my life easier. A few tips on Slack (and some alcohol) helped us through it. Some things I remember that might help you:

CSP header issue with Horizon

Horizon didn't work - it got blocked by CSP headers. That seemed to be a known issue described on "Content-Security-Policy frame-ancestors issue after installing Sitecore Horizon". And it was easy to fix - using web.config transformations.

GraphQL issues (with SXA)

We are using the GraphQL endpoint - which is recommended in this type of setup - but that didn't seem to work with integrated GraphQL. And you do want to use that as it is a very nice way to get data into your component. The solution was found in this kb: Nested placeholders are not rendered in Layout Service. Thank you Nick W. for you time and the solution this one.

Ok - so we removed that config file and it worked. And just for those amongst you that are container-newbies (like myself): you can delete such a file by adding this command in the Dockerfile (for cm and cd):
RUN Remove-Item -Path C:\inetpub\wwwroot\App_Config\Modules\SXA\Z.LayoutService\Sitecore.LayoutService.config -Force;


Image urls

To get image urls working, you need to set the setting IncludeServerUrlInMediaUrls to false. You need to do this on the right endpoint however. Even though we are using SXA, this is not the sxa-jss one, neither the jss one, but just the default one. Or you can patch all configs... 



Tips and tricks

  1. When working with GraphQL - don't just rely on the (many) blogs out there, as the schema for the GraphQL endpoint is different compared to what is used on those blogs based on older versions. This means that some things will not be present and you need to find other solutions (e.g. images and their url including parameters and a hash). This also means however some new things are available - like sorting and paging in search queries - out of the box.
  2. If your rendering contains (new) placeholders, you need to define these placeholders in the Layout Service Placeholders field in your rendering. To do this, you need to add a PlaceholderSettings item first. The field is a treelist that lets you select such placeholder setting item(s). However, it points towards the global placeholder settings folder in the layout section of Sitecore - but in SXA we define placeholders in the sites presentation area. A possible solution: get the ID of your placeholder setting item and paste it in the field in "raw values" mode.  
  3. Use partial and page designs - very simple trick but saved us a lot of time again.
  4.  If you need to access your Solr to test queries, you can find the ip address of the container with "docker container inspect <containername>". 

I'm quite sure there is more... so more posts will be coming in the next days, weeks...  right now we are still struggling with the Managed Cloud though 😒   

My apologies for all the text which is probably very chaotic.. just like my brain after a few weeks jumping into this containered JSS bath 😵 But if I saved anyone some time in their Sitecore endeavor it was worth it. 

Hoping to read more adventures on Sitecore JSS 10.2 with NextJS soon and see you all in Budapest.



Monday, June 28, 2021

Transfer Sitecore security settings with Powershell

Sitecore security settings

You can set security on each item in Sitecore for any number of roles (or users, but you shouldn't do that). A while ago I had to set the security for a particular role in a way that I actually changed a few dozen items. This was on a pre-production environment and related to a new site that was prepared in that environment. That new site is moved to the production environment with a package, and that will include the security settings on all the items in the site. But what with all the ones that are not in the site and that are also touched by adding a new security rule for this role? I couldn't move all those items and I don't want to do it manually...  so I came up with a script in Powershell. I'm posting it here for my future reference, but maybe someone else can use it as well...

Sitecore Powershell Extensions (SPE) to the rescue

I created a script that created a Powershell script. Executing the created script will set all the security rules that were found for a particular role in a give path.

function GetAssignedItems($path)
{
    Write-Host "Start: " $path
     
    $items = Get-ChildItem -Path $path -Recurse
    $rootItem = Get-Item -Path $path
    $items = $items + $rootItem
 
    foreach ($item in $items)
    {
        $acl = Get-ItemAcl -Identity "MYROLE" -Item $item
        if ($acl)
        {
            foreach ($rule in $acl)
            {
                "Add-ItemAcl -PropagationType " + $rule.PropagationType + " -SecurityPermission " + $rule.SecurityPermission + " -AccessRight '" + $rule.AccessRight +"' -Identity 'MYROLE' -Path '"+ $item.Paths.FullPath + "'"
            }
        }
    }
    Write-Host "Done."
}
 
GetAssignedItems "/sitecore/media library"

Just replace "myrole" with the name of your role and that's it. The call to the function takes the root you want to check.

This will generate output like this:
Add-ItemAcl -PropagationType Entity -SecurityPermission AllowAccess -AccessRight 'item:read' -Identity 'MYROLE' -Path '/sitecore/media library/Project'
Add-ItemAcl -PropagationType Entity -SecurityPermission DenyInheritance -AccessRight '*' -Identity 'MYROLE' -Path '/sitecore/media library/Project'

You can do this for any database - just note that the database is not added to the result output. 

This actually exports the rights - meaning it will generate a script that is able to set them again on any other environment. 

To import the rights into an environment, run the resulting script in a Powershell console in your Sitecore admin. This will set the security as required. Just make sure you run the script in the correct database ;)


Thursday, June 17, 2021

Sugcon 2021

 Global Virtual Sitecore UserGroup 2021

The 2021 SUGCON (Sitecore UserGroup Conference) was virtual 😿 and global 🌍.  

The sessions were streamed live on YouTube - you can (re-)view all the sessions at the Sugcon Global 2021 playlist.  I was honored to be amongst the presenters this year with a session on the multisite capabilities of SXA and how to even extend those to achieve a great multisite experience.

A big thank you to all the organizers for a smooth organization and the opportunity to share this knowledge.

 

Creating websites with SXA in 15 minutes



My presentation was about a project to provide a multisite setup for (smaller) websites that need to be created smooth and fast (competing against Wordpress and such). 

We managed to do this with ootb SXA and some customizations that are shown and also documented in these blogs (including all the code):

Sharing


Template enhancements

I hope you liked the session (and all the other ones from this years Sugcon) and may it inspire you to create amazing solutions.


And if anyone gets to do something similar with JSS I would like to hear about it... 

Wednesday, June 16, 2021

Sitecore Virtual Developer Day 2021

 Sitecore Virtual Developer Day 2021

In March of this Covid year 2021 Sitecore organized their 4th Virtual Developer day. 16 developer-driven sessions from Sitecore MVPs, community developer leaders and Sitecore evangelists. I was honored to be amongst the chosen presenters this year with a session on SXA and Scriban.

The full video of the whole developer day can be found on YouTube. It's one big video for the full 8 hours of content, but you can easily browse the chapters if you want to view a particular session.


Scriban for SXA with unlimited potential

The slides of my session:



The presentation has references to a few blog posts:


And if you are interested in Scriban and SXA, make sure to read the official documentation first.


I hope you enjoyed the Virtual Developer day and my session - if you have any questions, contact the community at StackExchange 😉

Monday, January 25, 2021

SXA cache multilingual datasources

Sitecore SXA datasource caching

I bumped into an issue a while ago related to datasources and how they are handled in a Sitecore SXA environment. This is an overview of our setup:

  • using SXA (Sitecore Experience Accellerator) for Sitecore 9.2
  • a PageList component with a variant
  • an Item Query that refers to custom code (a query starting with "code:" - more info here)
  • custom code that return a list of items and is language dependent (meaning: the list of items is different per language)  

That last one is actually the trigger for my issue as I had used custom coded Item Queries before and they seemed to work fine. Even in a multilingual setup. But in this particular case the results per language were actually different items - not just the same items with translated content.

I noticed that there was some caching issue and in the end I contacted Sitecore Support and placed some information on Sitecore StackExchange: "Custom SXA PageList datasource is not working in multilingual environment".


ResolveRenderingDatasourceCache

With some research I found there was an issue with the ResolveRenderingDatasourceCache. That cache is not language dependent and so my results were wrong.

A first possible solution would be to disable that cache by patching the config setting XA.Foundation.LocalDatasources.ResolveRenderingDatasourceCache.Enabled to false. This works, but of course that turns off this cache completely.

I noticed that the resolveRenderingDatasource pipeline has 2 relevant functions:
  • Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.GetFromCache
  • Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.SetCache
This brings us to solution two, which would disable the cache only for datasources that start with "code:": we can override the process method in the SetCache class and add code like this:
if (args.DefaultDatasource.StartsWith("code")) { return; }
This works as well, we keep the cache for most of our datasources but it is still disabled for the coded ones. And so I thought of another final solution: let's check the generation of the cache key!

BuildCacheKey

Both methods (GetFromCache and SetCache) are using a BuildCacheKey method from an underlying ResolveRenderingDatasourceCacheBase class. This method seemed not to be used anywhere else so I decided to override that one and use it in both the GetFromCache and SetCache classes.
protected override string BuildCacheKey(ResolveRenderingDatasourceArgs args)
{
    var key = base.BuildCacheKey(args);
    if (!string.IsNullOrEmpty(key) && args.DefaultDatasource.StartsWith("code:", StringComparison.OrdinalIgnoreCase))
    {
        key += Sitecore.Context.Language.Name;
    }

    return key;
}
This solution makes the cache work in all situations. We keep the current implementation for most datasources (as those are not language dependent) and add the language to the key for those that might (the ones starting with "code:" and you might also add those starting with "query:" if needed).
 
Patch the processors as:
<sitecore>
  <pipelines>
    <resolveRenderingDatasource>
      <processor type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.GetFromCache, Sitecore.XA.Foundation.LocalDatasources" resolve="true">
        <patch:attribute name="type">Feature.Caching.Rendering.Datasources.GetFromCache, Feature.Caching</patch:attribute>
      </processor>
      <processor type="Sitecore.XA.Foundation.LocalDatasources.Pipelines.ResolveRenderingDatasource.SetCache, Sitecore.XA.Foundation.LocalDatasources" resolve="true">
        <patch:attribute name="type">Feature.Caching.Rendering.Datasources.SetCache, Feature.Caching</patch:attribute>
      </processor>
    </resolveRenderingDatasource>
  </pipelines>
</sitecore>

A change request has been made to adjust this in a future version of SXA - but in the meantime you can easily patch it yourself if needed. Keep the cache and enable different results in different languages!


 

Thursday, January 21, 2021

Sharing available renderings in multisite SXA

Sitecore SXA available renderings

We have a multisite setup in SXA to easily create (small) websites and have been sharing a lot of configuration, layout information and content between those. Check my latest SXA blog posts for more information.

We did already cover sharing placeholder settings. But if we can share those, we might as well share the available renderings configuration. This is a list of renderings that is defined per site to define the availability within the SXA Toolbox - and since SXA 1.8 we can also define the structure of the toolbox with these Available renderings. As explained in the documentation, you have to check the box "Group renderings in sections according to Available Renderings items in the site" to do so.


I believe it is a best practice to do this so that feature is always on in my projects.

But let's get back to sharing...  We have a shared site (and master site) setup, so it would be a good idea to define the available renderings in the shared site and not in every site (created from the master site). This idea was on my radar and I got triggered by Toby Gutierrez to really try this now.


Sharing available renderings

I am going to try the same principle I used for the placeholder settings: if the current site has no configuration in place -meaning no children underneath the available renderings parent node- we check the shared site(s) and get the configuration there.

The toolbox

First of all, we need to figure out how the toolbox works. There is a handler (sxa-toolbox-feed.ashx) but that is not to much use for us here as it works with data that is retrieved elsewhere. I noticed however that in case the renderings grouping is turned on, a AvailableRenderingsOrderingService is called. This service will read the configuration and use this to structure the toolbox data that was passed along. As a side affect, it will also filter (again) on the available renderings.

This means that if you are using the grouping -as I do- you will have to override this. Let's do that:
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.DependencyInjection;
using Sitecore.XA.Foundation.Editing.Models;
using Sitecore.XA.Foundation.Multisite;
using Sitecore.XA.Foundation.Presentation;
using Sitecore.XA.Foundation.SitecoreExtensions.Extensions;
using Sitecore.XA.Foundation.SitecoreExtensions.Repositories;

namespace Feature.Multisite.Editing
{
  public class AvailableRenderingsOrderingService : Sitecore.XA.Foundation.Editing.Service.AvailableRenderingsOrderingService
  {
    public AvailableRenderingsOrderingService(
      IPresentationContext presentationContext,
      IMultisiteContext multisiteContext,
      IContentRepository contentRepository) : base(presentationContext, multisiteContext, contentRepository)
    {
    }

    public override IList<AvailableRenderingEntry> GetOrderedRenderings(Item siteItem, IList<Item> renderings = null)
    {
      var orderDictionary = new Dictionary<ID, ValueTuple<int, string>>();
      var presentationItem = PresentationContext.GetPresentationItem(siteItem);
      var folder = presentationItem?.FirstChildInheritingFrom(Sitecore.XA.Foundation.Presentation.Templates.AvailableRenderingsFolder.ID);
      if (folder != null)
      {
        if (!folder.Children.Any())
        {
          var shared = ServiceLocator.ServiceProvider.GetService<ISharedSitesContext>().GetSharedSitesWithoutCurrent(siteItem);
          foreach (var site in shared)
          {
            presentationItem = PresentationContext.GetPresentationItem(site);
            folder = presentationItem?.FirstChildInheritingFrom(Sitecore.XA.Foundation.Presentation.Templates.AvailableRenderingsFolder.ID);
            if (folder != null && folder.Children.Any())
            {
              break;
            }
          }
        }

        if (folder != null)
        {
          var sections = (CheckboxField)folder.Fields[Sitecore.XA.Foundation.Presentation.Templates.AvailableRenderingsFolder.Fields.GroupRenderingsInSections];
          if (sections != null && sections.Checked)
          {
            var array = folder.Children.Where(item => !string.IsNullOrEmpty(item[Sitecore.XA.Foundation.Presentation.Templates._RenderingsList.Fields.Renderings])).ToArray();
            for (var index1 = 0; index1 < array.Length; ++index1)
            {
              var fieldRenderings = (MultilistField)array[index1].Fields[Sitecore.XA.Foundation.Presentation.Templates._RenderingsList.Fields.Renderings];
              for (var index2 = 0; index2 < fieldRenderings.TargetIDs.Length; ++index2)
              {
                if (!orderDictionary.ContainsKey(fieldRenderings.TargetIDs[index2]))
                {
                  orderDictionary.Add(fieldRenderings.TargetIDs[index2], new ValueTuple<int, string>((index1 + 1) * 1000 + index2, fieldRenderings.InnerField.Item.DisplayName));
                }
              }
            }
          }
        }
      }

      return renderings?.Select(r => new AvailableRenderingEntry
      {
        RenderingItem = r,
        Order = orderDictionary.ContainsKey(r.ID) ? orderDictionary[r.ID].Item1 : 0,
        SectionName = orderDictionary.ContainsKey(r.ID) ? orderDictionary[r.ID].Item2 : r.Parent.DisplayName
      }).OrderBy(r => r.Order).ToList();
    }
  }
}  
This code is almost identical as the original one. The difference is in the GetOrderedRenderings function where we check the existance of a local configuration (folder.Children.Any) and try the shared sites (using the SharedSitesContext) if needed.

The service is registered in the config, so we patch that config to include our version:
<sitecore>
  <services>
    <register serviceType="Sitecore.XA.Foundation.Editing.Service.IAvailableRenderingsOrderingService, Sitecore.XA.Foundation.Editing"
              implementationType="Sitecore.XA.Foundation.Editing.Service.AvailableRenderingsOrderingService, Sitecore.XA.Foundation.Editing" lifetime="Singleton">
      <patch:attribute name="implementationType">Feature.Multisite.Editing.AvailableRenderingsOrderingService, Feature.Multisite</patch:attribute>
    </register>
  </services>
</sitecore>

That's it. Now we can safely delete all children from the Available renderings node in our sites that use our shared site. The toolbox in those sites will use the configuration defined in the shared site.


But...

Note that this only works if the grouping is enabled. As I always do this, I'm not going to try to implement a solution if it's not. This also means that the toolbox handler might be getting too much renderings as a parameter, but the result is ok so that's fine for me. 

However, if you do require such a solution without the grouping you'll need to check the getPlaceholderRenderings pipeline and especially the GetModuleRenderings processor. This has a function called GetSiteRenderingSources and you'll have to add the same logic there as I used in the AvailableRenderingsOrderingService.


Conclusion

Yet another configuration that we can share across all the created sites...  this makes it easier to handle if we add/remove components that can be used on those sites - we only have to do this on one spot from now on.