One Performance Blog to Rule them all – Combining the 6 Pillars of Speed

I have done a number of posts and talks at user groups on Page Speed and performance over the last few years. I have split the various topics into individual blog posts for the most part as performance is dependent on many factors. What has really been missing is a complete demo of how all the different techniques come together to give your site a really good score. So that’s what I intend to demo here is the combination of the 6 pillars of page speed in one Sitecore instance. To recap here are the 6 pillars of page speed performance in my opinion:

1) Introduce image lazy loading

2) Ensure a cache strategy is in place and verify its working. (must have adequately sized production servers)

3) Deploy image compression techniques

4) Use responsive images (must serve up smaller images sizes for mobile)

5) Introduce Critical CSS and deferred CSS files

6) Javascript is not a page speed friend. Defer Defer Defer

I have shown a subset of these previously but crucially three critical pillars to do with imaging were hard to achieve at the time. This is now possible due to being able to support Next Gen image compression (webp), which I wrote about in my previous blog. With a little more time and investigation Image Lazy Loading, responsiveness and image compression to give a more complete picture of how each pillar impacts page speed.

Here are the tools and blogs I will use to achieve each of these:

1) Image Lazy Loading – Blog post by MVP Sitecore SAM and https://github.com/thinker3197/progressively

2) SXA Cache Settings – SXA official documentation

3) Next Image (WEBP) Image Compression – https://github.com/Aceik/ImageCompression

4) SXA Responsive Images – SXA official documentation

5) Introduce Critical CSS and deferred CSS files – https://github.com/Aceik/Sitecore-Speedy

6) Javascript is not a page speed friend. Defer Defer Defer – https://github.com/Aceik/Sitecore-Speedy

Alternatives: Mark Gibbons (MVP) recently upgraded the Dianoga image library to support WEBP. Worth a look if you don’t want to use a third party API. It also supports a CDN. Also Vincent Lui (MVP) also pointed out in his recent SUGCON talk, you can achieve both image compression and image lazy loading via some of the modern CDN’s. That is a great (easy) option if you are retro fitting these techniques to a live website.

I’m not going to dive deep into exactly how to setup each of these things as I think the individual links have sufficient instructions. I will show in the Demo videos how each pillar impacts the HTML rendered. For the most part I am keen to demonstrate the impact of each of these line items and how each one will benefit your page speed score.

Before we begin its important to understand that the algorithm (Lighthouse) behind Google’s Page Speed insight doesn’t work in an exactly linear fashion. If you improve your score by ticking off one of the above, don’t expect ticking off another issue will have the same benefit. The last 20 points out of 100 (on the mobile scoring system) is that hardest to achieve based on what I have seen.

Live Demo Video Series that accompanies this blog:


Test Outline:

Google Page Speed Insights — Scores can fluctuate widely based on network latency. At time you will experience score fluctuations at different times of the day on the same site.

In general this is a guide

Here is the general outline of the VM that hosted the IIS instance for testing. I also put the VM under some basic load while running the tests.

  • All the test below used Sitecore 9.3 and the SXA habitat example site.
  • Test used the live Google Page Speed insights tool via the url: https://developers.google.com/speed/pagespeed/insights/
  • Sitecore was setup on an Azure VM with the specifications:undefined
  • The test was run 5 times, to get an average score.
  • The test page was the homepage of the Habitat site and the page was requested before running the test 5 times so that the instance could be considered warm.
  • EXM and XDB were not running on these test instances.
  • Test results are Mobile Page Speed scores only – This is the most important metric in today environment and good desktop scores are not really a challenge.
  • The default Habitat cache rendering for Navigation was left on for all tests. (without this the site fails under basic load altogether)
  • All tests were conducted under load in an attempt to replicate a production environment. For this I used a node package called loadtest.
  • SXA CSS/Javascript optimisations were turned on, but as I have mentioned before this has a minimal performance boost.

loadtest -c 10 –rps 10 http://baselinecd.dev.local/

10 requests per second with a concurrency of 10

Baseline Score

The Baseline score encompasses the habitat site installed with no modifications.

Result: 48 / 38 / 40 / 34 / 38 = 39.6/100 Average

Observation: Heavily penalised for CSS and Javscript loading times.


Image Lazy Loading

All images on the homepage were converted to be Lazily loaded. A single large blurred image was used as the placeholder for all images.

Result: 57 / 55 / 61 / 52 / 63 = 57.6/100 Average

Observation: Around the mid point of the scale, image lazy loading has around a 15 – 20 point impact.


Rendering Cache Strategy

I have blogged extensively about this in the past but setting up cache settings properly is so critical and has a major impact. Its also one of the easiest things to fix for a poorly performing Sitecore site. Also note the only way to accurately demonstrate the impact that Rendering cache has on a site is to test it under load.

This test was run with higher user per second: loadtest -c 10 –rps 30 http://baselinecd.dev.local/

With Cache Enabled:

49 / 56 / 41 / 54 / 54 = 50.8

Without Cache Enabled:

ERR_TIMED_OUT / ERR_TIMED_OUT / ERR_TIMED_OUT / ERR_TIMED_OUT / ERR_TIMED_OUT = You get the point 🙂

Observation: Rendering cache settings are critical and should be the first step in Page Load Speed refinement for a Sitecore site. 10 Point benefit observed once a site is stable under load.


Image Compression

Result: 60 / 58 / 61 / 62 / 62 = 60.6/100 Average

Observation: Around the mid point of the scale, image lazy loading has around a 20 point impact.


Critical CSS

Result: 74 / 78 / 79 / 81 / 81 = 78.6/100 Average

Observation: The combination of critical CSS in the head and Deferred styles provides a meaningful page speed boost. 25 Point observed benefit.


Deferred Javascript

Result: 92 / 94 / 93 / 94 / 94 = 93.4/100 Average

Observation: Javascript has a massive impact, reducing it drastically in the initial payload provides massive page speed improvements. 40 Point observed benefit.

You might think, hey I will just do Deferred Javascript and it will be all good. While this particular PIllar/Criteria does have the biggest impact. Every site is different and as mentioned earlier scores fluctuate. The upper part of the scoring system is the hardest to reach. So while this is a great starting point, ignore the other speed pillars at your peril.


Responsive Images

Result: 56 / 54 / 59 / 56 / 60 = 57/100 Average

Observation: Around the mid point of the scale converting images to be responsive (srcset support) has about a 10 point impact.


Results Summary

CriteriaAverage ScoreObserved Benefit
No Change (SXA Habitat Home OOTB)41.8 / 100
Image Lazy Loading57.6 / 10015 Points
Sitecore Rendering/HTML Cache Settings50.8 / 10010 Points
Image Compression (webp)60.6 / 10020 Points
Critical CSS78.6 / 10025 Points
Deferred Javascript93.4 / 10040 Points
Responsive Images57 / 10010 Points

The Pillars Combined

In isolation we can see the rough results of what each of the pillars might do to our Page Speed. The real question is what does combining all these pillars produce.

Result: 100 / 100 / 100 / 100 / 100 = 100/100 Average

Observation: Do I expect this on an actual production site realistically ? That is certainly the dream, but in reality you should be over the moon if you make it into the 90s and pat your self on the back if you get into the 80s as well. For any Sitecore site if you make it into the 90’s for mobile, your doing an amazing job.

Admittedly for the combined demo I skipped the responsive image pillar. SXA supports Responsive Images but not in combination with data attributes. It was going to be a bunch of work to write a custom SXA handler to support both lazy loading and responsiveness at the same time. That is not to say its not possible. Either way the impact was minimal.

Conclusion

Page speed is so critical to SEO and visitor conversion. A slow site instantly turns away users on mobile and tablet devices. Admittedly the final result shown above and in the video have required that all the right tools be available to the Sitecore community. Which up until recently you likely needed to bake your own solutions in order to get that over the line.

I think its now becoming possible to aim fairly high (90/100 on mobile) with our Page Speed scores, but it does require getting most if not all of the Architecture Pillars above working together. Its worth learning each of these and understanding the pitfalls and limitations if you want really great page speed. Good luck and feel free to get in touch with any questions.

Footnote

The combined pillars can produce great results but you still need to load test before going live. Checkout the video below where I search for the breaking point using the loadtest tool. Please note that this node based load test tool should just be used for a guide. Before go live I recommend using a hosted load tool solution that has multiple geographic locations. Tests done based on one network location or device will result in a network bottle neck and give you false positives.

Bonus Video: https://www.youtube.com/watch?v=96YcxyhYh0U

SXA Speedy – Supercharge your SXA Page Speed Scores in Google

We are excited to preview our latest Open Source module. Before jumping into the actual technical details here are some of the early results we are seeing against the Habitat SXA Demo.


Results:

Results

Before:

After

After:

Before
* Results based on Mobile Lighthouse Audit in chrome. 
* Results are based on a local developer machine. Production results usually incur an additional penalty due to network latency.

Want to know more about our latest open source SXA Sitecore module …. read on ….


I’m continually surprised by the number of new site launches that fail to implement Google recommendations for Page Speed. If you believe what Niel Patel has to say this score is vitally important to SEO and your search ranking. At Aceik it’s one of the key benchmarks we use to measure the projects we launch and the projects we inherit and have to fix.

The main issue is often a fairly low mobile score, desktop tends to be easier to cater for. In particular, pick out any SXA project that you know has launched recently and even with bundling properly turned on its unlikely to get over 70 / 100 (mobile score). The majority we tried came in somewhere around the 50 to 60 out 100 mark.

Getting that page score into the desired zone (which I would suggest is 90+) is not easy but here is a reasonable checklist to get close.

1) Introduce image lazy loading
2) Ensure a cache strategy is in place and verify its working.
3) Dianoga is your friend for image compression
4) Use responsive images (must serve up smaller images sizes for mobile)
5) Introduce Critical CSS and deferred CSS files
7) Javascript is not a page speed friend. Defer Defer Defer

The last two items are the main topics that I believe are the hardest to get right. These are the focus of our new module.

Critical_plus_defer

Check out the GitHub repository.

I have also done an installation and usage video.

So how will the module help you get critical and JS defer right?

Deferred Javascript Load

For Javascript, it uses a deferred loading technique mentioned here. I attempted a few different techniques before finding this blog and the script he provides (closer to the bottom of the article) seems to get the best results.  It essentially incorporates some clever tactics (as mentioned in the article) that defer script load without compromising load order.

I also added in one more technique that I have found useful and that is to use a cookie to detect a first or second-time visitor. Second-time visitors naturally will have all external resources cached locally, so we can, therefore, provide a completely different loading experience on the 2nd pass. It stands to reason that only on the very first-page load we need to provide a deferred experience.

Critical + Deferred CSS Load

For CSS we incorporated the Critical Viewport technique that has been recommended by Google for some time. This technique was mentioned in this previous blog post. Generating the Critical CSS is not something we want to be doing manually and there is an excellent gulp based package that does this for you.

It can require some intervention and tweaking of the Critical CSS once generated, but the Gulp scripts provided in the module do seek to address/automate this.

Our module has a button added into the Configure panel inside the Sitecore CMS. So Content Editors can trigger off the re-generation of the Critical CSS when ever needed.

Generate Critical button added to Configure.

Local vs Production Scores

It’s also important to remember that the scores you achieve via Lighthouse built into Chrome on localhost and your non-public development servers can be vastly different than production. In fact, it’s probably safest to assume that non-production boxes give false positives in the region of 10 to 20 points. So it’s best to assume that your score on production will be a little worse than expected.

Conclusion

It’s a fair statement that you can’t just install the module and expect Page Load to be perfect in under 10 minutes.  Achieving top Page Load Speed’s requires many technical things to work together. By ensuring that the previously mentioned checklists are done (Adequate Servers, Sitecore Cache, Image Loading techniques) you are partway over the line. By introducing the deferred load techniques in the module (as recommended by Google) you should then be a step closer to top score.

For more hints please see the Wiki on Github.

This module has been submitted to the Sitecore Marketplace and is awaiting approval.


Author: Thomas Tyack – Solutions Architect / Sitecore MVP 2019

Creating a custom SXA rendering with variants

In this blog post we’ll step through the process of creating a custom SXA rendering with variants.  For this example, we’ll create a rendering that is used to display information about cars.  By using a rendering with variants, we can add a Car rendering to our page, and depending on which variation we choose, we can display it with a different appearance and different fields.  We’ll create a Car rendering with 3 variations:

  • Summary
  • Specifications
  • Full Details

Template

The first thing we need to do is create the template.  Even though some of the variations only display some of the fields, the template will have all of the fields for a car, and the variations will use only the fields they need.

Template

This template was created in /sitecore/templates/Feature/Cars/Car.

Rendering

The next step is to create the rendering.  Ours was created at /sitecore/layout/Renderings/Feature/Cars/Car.  The rendering should be a Controller Rendering.  Set the Controller (this one has been set to “Car”), and set the Controller Action to “Index”.  Set the Datasource Template to be the template we’ve just created (in this case, /sitecore/templates/Feature/Cars/Car), and the Datasource Location to “query:$site/*[@@name=’Data’]/|*[@@templatename=’Car’]”.  It’s important to set “Editable” to true so that the rendering will be available in the Toolbox.

Rendering

To make the rendering appear in the toolbox, we need to add it to Available Renderings.  The Available Renderings folder is  found under the Presentation folder of your SXA site.  First add an Available Renderings item under the Available Renderings folder.  In this case, I’ve added one called Cars.  Once you’ve added the Available Renderings item, click “Edit” on the Renderings field, and add your new rendering (“Car”) to the list of renderings.

AvailableRenderings

It doesn’t really matter what you call the Available Renderings item, or which Available Renderings item our rendering is added to, because the Available Renderings item is simply for filtering/organising the rendering in the content tree.  It doesn’t actually determine where it appears in the Toolbox.  The folder structure of where the rendering is located determines this.  For example, because this rendering is at /sitecore/layout/Renderings/Feature/Cars/Car, it will appear in the Toolbox under a new category of “Cars”.

Toolbox

Rendering Variants

This is where we setup the 3 variations of the rendering.  To start off, create a new Variants item called “Car” under the Presentation/Rendering Variants folder of your SXA site. Under that item, create 3 Variant Definition items for each of the variants we need.

When the variants have been added, they can be selected from the Variant dropdown list in the Experience Editor:

ListVariants

Under each of the Variant Definitions, we need to insert the fields to be displayed.  The desired layout of these fields will determine how we add the fields.  The fields can be grouped into sections, and the order and hierarchy of the fields directly matches the order and hierarchy of the HTML elements that get rendered.  This, coupled with the “Css Class” field allows you to have full control over the layout and appearance of the variant.   The beauty of this is that once this initial setup has been done, you can add as many variants to this rendering as you like without having to write any more code at all.

When adding the fields, it is important that the value of the “Field name”  matches the name of the field in the template.

As an example, the Summary variant has been created using this following structure:

SummaryVariant

The Css Class for the Image field has been set to “col-xs-3”, and the Css Class for the Car Details section has been set to “col-xs-9”.  The Tags for all of these items are using the default of “div”.  This configuration translates to the following HTML:

<div class="component car">
   <div class="component-content">
      <div class="col-xs-3 field-image">
         <img style="margin:0;" src="/-/media/458B8369BE6D40489797D75DDA8BB79D.jpg?h=114&la=en&w=200&hash=641FD08747F050B6AF1779E77E5BF2C24AB2CEC0" alt="" width="200" height="114" />
      </div>
      <div class="col-xs-9">
         <div class="field-make">Holden</div>
         <div class="field-model">Commodore</div>
         <div class="field-year">2007</div>
      </div>
   </div>
</div>

And it looks like this:

SummaryVariantRendered

As an example, we can have a completely different look for the same rendering by selecting the Full Details variant, which has been setup like this:

FullDetailsVariant

And it ends up looking like this:

FullDetailsVariantRendered

There’s a great article that covers this in more detail at http://www.markvanaalst.com/sitecore-experience-accelerator/the-power-of-the-variants/

Code

This section covers the code that needs to be created in your solution.

First add a new Feature module to your solution, then add the following items:

IRepository

using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace YourBusiness.Feature.Cars.Repositories
{
   public interface ICarRepository : IVariantsRepository
   {
   }
}

Repository

using YourBusiness.Feature.Cars.Modes;
using Sitecore.XA.Foundation.Mvc.Repositories.Base;
using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace YourBusiness.Feature.Cars.Repositories
{
 public class CarRepository : VariantsRepository, ICarRepository
  {
     public override IRenderingModelBase GetModel()
     {
         CarModel model = new CarModel ();
         FillBaseProperties(model);
         return model;
     }
  }
}

Model

No need to add our template fields in this instance

using Sitecore.XA.Foundation.Variants.Abstractions.Models;

namespace YourBusiness.Feature.Cars.Models
{
   public class CarModel : VariantsRenderingModel
   {
   }
}

Controller

Keep in mind that the controller must inherit from Sitecore.XA.Foundation.Mvc.Controllers.StandardController.  The Standard controller will handle the routing with default action result “Index”, so there’s no need to add our own, although we can override “Index” if we need to.

using YourBusiness.Feature.Cars.Repositories;
using Sitecore.XA.Foundation.Mvc.Controllers;
using Sitecore.XA.Foundation.RenderingVarients.Repositories;

namespace YourBusiness.Feature.Cars.Controllers
{
   public class CarController: StandardController
   {
       protected ICarRepository CarRespository
       {   get; set;   }

       protected IVariantsRepository VariantsRespository
       {   get;  set;  }

       public CarController(ICarRepository, carRepository, IVariantsRepository variantsRepository)
       {
           this.CarRespository = carRepository;
           this.VariantsRespository = variantsRepository;
       }

       protected object GetVariantsModel()
       {
           return VariantsRespository.GetModel();
       }

       protected override object GetModel()
       {
          return CarRepository.GetModel();
       }
   }
}

View

It is important that all SXA components should be wrapped in “component” and “component-content” divs. “Model.Attributes” adds styles from the rendering parameters.

@model YourBusiness.Feature.Cars.Models.CarModel

@if (Model.DataSourceItem != null || Html.Sxa().IsEdit)
{
<div>
   <div class="component-content">
      @if (Model.DataSourceItem == null) 
      { 
         @Model.MessageIsEmpty 
      } 
      else 
      { 
         foreach (VariantFieldBase variantField in Model.VariantFields) 
         { 
            @Html.RenderingVariants().RenderVariant(variantField, Model.Item, Model.RenderingWebEditingParams) 
         } 
      }
   </div>
</div>
}

Config

<?xml version="1.0" encoding="utf-8"?>

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
    <sitecore>
        <services>
            <configurator type="YourBusiness.Feature.Cars.Pipelines.RegisterCarServices, YourBusiness.Feature.Cars" />
        </services>
    </sitecore>
</configuration>

]

Pipeline

Here we register the dependencies.

using Microsoft.Extensions.DependencyInjection;
using YourBusiness.Feature.Cars.Repositories;
using Sitecore.DependencyInjection;
using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace YourBusiness.Feature.Cars.Pipelines
{
   public class RegisterCarServices : IServicesConfigurator
   {
      public void Configure(IServiceCollection serviceCollection)
      {
         serviceCollection.AddTransient<ICarRepository, CarRepository>();
         serviceCollection.AddTransient<IVariantRepository, VariantRepository>();
      }
   }
}

 

SXA Installation Pitfalls

When installing SXA on a fresh Sitecore instance, things will generally go pretty smoothly, however, when installing SXA on an existing Sitecore instance with customisations, it’s not necessarily quite so simple.  Here’s a summary of some of the issues that were encountered when recently installing SXA 1.4 onto a Sitecore 8.2.4 instance.  Hopefully this post will help if you encounter similar issues.

Dependency Injection

In this instance, there was code that was registering dependencies using wildcards.  Specifically, it was adding (among others) assemblies matching the pattern “*.Feature.*”.  This was wrongly picking up SXA assemblies and was giving errors in the Experience Editor like:

Error Rendering Controller: BrowserTitle. Action: Index: Could not create controller: 'BrowserTitle'.

To fix this, the Dependency Injection code was altered to exclude all assemblies matching the pattern “*.XA.*”.

Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll

The Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll file packaged in SXA 1.4 is an older version than what is being used throughout the solution we’re updating, so when trying to install the package, the installation failed halfway through. To fix this, the SXA install package was altered using 7-zip to replace the included DLL with the newer version.

Missing Rendering IDs (no null check)

This issue may be specific to this instance, and might be due to invalid data, but it’s worth mentioning, since SXA is not doing a null check and this problem may crop up for you, too.

When viewing a particular page, the following exception was thrown:

Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.XA.Feature.Composites
at Sitecore.XA.Feature.Composites.Pipelines.GetXmlBasedLayoutDefinition.InjectCompositeComponents.GetCompositeComponents(XElement layoutXml)
at Sitecore.XA.Feature.Composites.Pipelines.GetXmlBasedLayoutDefinition.InjectCompositeComponents.Process(GetXmlBasedLayoutDefinitionArgs args)

This turned out to be some renderings that had no ID, and SXA wasn’t doing a null check. The specific xmlLayout is shown below:


Editing the raw values and removing the renderings with a null ID fixes this problem.

To check if there were any other pages with this same problem, a small powershell script was written:

$items = Get-ChildItem -Path "master:/sitecore/content/Consumer/Home" -Recurse
foreach($item in $items) {
$renderings = Get-Rendering -Item $item
foreach($rendering in $renderings) {
if($rendering.ItemID -eq $null) {
Write-Host Item: $item.DisplayName $item.ID
}
}
}

Media Library/Project Folder

The SXA installation wants to create its own “Media Library/Project” folder, using a specific ID. If this item with the specific ID doesn’t exist, SXA will fail when creating a Tenant or Site.   Your instance may already have a folder by this name, in which case it will need to be renamed before the SXA installation, then it’s contents moved into the folder created by SXA.

Duplicate Navigation Controllers

For this instance, when adding the SXA navigation rendering to a page, the following exception occurs:

Multiple types were found that match the controller named 'Navigation'. This can happen if the route that services this request ('{*pathInfo}') does not specify namespaces to search for a controller that matches the request.

There may be other similar controllers in your instance where you get the same problem.