Next Gen Image Compression in Sitecore

Spoiler: This post is not a post about Dianoga, I take a deep dive into Tiny PNG and Kraken.IO integrations into Sitecore. The results are worth checking out at the bottom.

At the start of the year, I’ve picked up where I left off, on page speed. Last year I took a deep dive into attempting to improve the page speed on Sitecore SXA sites by using some of Google’s recommended techniques to structure the page. If you haven’t already seen it, head on over the Sitecore Speedy and see some of the results we achieved.

I’ll be the first to admit that getting really good page speed scores isn’t easy. It takes a lot of different factors to come together. Just as a reminder, here is the main list that I would consider you need to check off to be winning at this game.

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

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

For this post, i’m going to look at an alternative to Dianoga. I’m a big fan of Dianoga and have used it over the years to crunch loads of oversized images introduced by Content Editors. I will, however, say that it can add complexity to deployments and CI/CD pipelines and while some claim to have had success in Azure Apps, others have not.

On the flip side, content editors love Tiny PNG, which is one of the most popular image compression website utilities going around. Tiny PNG also has a developer API, so we have used this to build in a compression tool that can be used directly from your Sitecore toolbar.

The button below is hooked up to chat to Tiny PNG API. It will send across your image data and receive a compressed image back for storage.

Full disclosure, I’m not the first person to hook up Tiny PNG to the image library. I could find two other implementations

One will allow you to run a powershell script to connect to the Tiny PNG API and the other is a module to connect to the API on upload.

This implementation of the Tiny PNG API introduces the following variances:

  • A button in the CMS to crunch any single image.
  • A scheduled task that will process any image not already processed.
  • Error handling for when the API limits are reached
  • Logging that outlines which images were processed.
  • Before and After compression information stored in any Image field of choice.
  • A feature toggle to turn the whole feature on/off

All the source code is available at:

Now let’s jump in have a look at the results just from crunching a few images down:

Without image compression:

Click to Enlarge Image

To compress the images on the page, we head on over to the “Compress” button in the Media tab that we have introduced.

Click to enlarge

A few examples of compression results taken from homepage images:

Before: 158.4 KB | After: 110.6 KB

Before: 197.8 KB | After: 135.3 KB

Before: 640.0 KB | After: 120.7 KB

After compressing all the images on the page the saving can be seen below.

Click to enlarge

So our total image size saving is 2.4MB – 1.3MB = 1.1MB

A pretty decent saving from just pressing the compress button on 27 homepage images. Also, consider that the user won’t notice any difference in image quality as this method uses lossless compression.

The compression achieved is great for helping us tick off one of the requirements for fast pages with Google. But as we are about to find out Google will likely still complain about two other criteria. When it comes to Google Page Speed insights a page that does not have properly processed images will bring up the following three recommendations:

Here is a break down of how we address each one:

  1. Serve image in next-gen formats – Image formats like JPEG 2000, JPEG XR, and WebP often provide better compression than PNG or JPEG, which means faster downloads and less data consumption. Learn more.
  2. Properly Size images – Your CSS layouts should be responsive and use modern image retrieval techniques that adapt the image size requested based on screen size. Read More
  3. Efficiently encode images – The Tiny PNG integration above will take care of this. This is all about compressing the image to as small as it can get without a visible loss of quality.

So assuming you have already achieved number three using the Tiny PNG integration or another source, let us look at how we can solve the next-gen image requirement.

As a quick side note the testing I did after converting the images to next-gen also ticked item number two above. I don't think this should be relied on however and its best to incorporate responsive images into your projects from the beginning.  

When looking into how to convert images to a next-gen format I opted to target webp. Google has a nice little page explaining the format here.

WebP is natively supported in Google Chrome, Firefox, Edge, the Opera browser, and by many other tools and software libraries.

Once again I opted to look for an API that would provide the conversion for me so that Sitecore could easily connect, send the image and then store the result. All without any extra hosting requirements. I opted to go with Kraken.IO image APIs as they have a free 100MB trial offer and well free is a good price when building proof of concepts. The integration is all available on Aceik’s github repository. Just signup for your own API keys add them to the module settings (in the CMS) and start converting.

To test out just how much this would impact the image payload size for the whole page, I once again converted all the images on the SXA habitat homepage.

Here are the results:

Click to enlarge

So our total image size saving is now 2.4MB – 0.79MB = 1.61MB

The reduction in size from a non-compressed image to a webp formatted image is truly impressive.

A few examples:


I can only conclude by saying that if page speed is really an important factor for your Sitecore project take a look at Tiny PNG. If you want to go next level with your image formats and achieve great compression try out the Kraken.IO API integration as it could be well worth the small subscription fee.


CompressionTotal Image SizeSaving
None2.4 MB
Tiny PNG1.3 MB1.1MB
Kraken.IO (webp)0.79MB1.61MB


The module and code mentioned in this blog post are available on Aceik’s Github account. This also contains installations instructions.


After installation, your content editors will simply be able to compress and convert images as needed from within the CMS.

Click to enlarge

The Github Readme contains a run down and the standard settings inside Sitecore as shown below:

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 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.


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.


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

Part 3: External Tracking via FXM and Google Client ID

In this third part of our Experience Profile customisation series, we look at how we might integrate FXM into a third party website.  For the purposes of this blog, we assume the third party website is not built with Sitecore.

You can view Part 1, Part 2 and  Part 4 via the respective links.

A great example of where you might want to do this is if you link off to a third party shopping cart or payment gateway. In this particular scenario, you can use FXM to solve a few marketing requirements.

  • Pages Viewed: Track the pages the user views on an external site.
  • Session Merge: Continue to build the user’s Experience Profile and timeline.
  • Personalise content blocks in the checkout process.  Great for cross promotion.
  • Fire off goals at each step of the checkout process.
  • Fire off goals and outcomes once a purchase occurs.
Note: In the examples that follow we also show what to do in each scenario for single page application. View the footnote for more details about how you might support these with regards to FXM.

So let’s now examine how each requirement can be solved.

Pages Viewed

Page views are a quick win, simply injecting the beacon will record the page view.

For a single page application, each time the screen changes you could use:

SCBeacon.trackEvent('Page visited')

Session Merge

If you inject the Beacon on page load you get some session merging functionality out of the box. If you have a look at the compatibility table for different browsers it’s worth noting that Safari support is limited.

Here is a potential workaround for this notable lack of Safari support:

  • Follow the instructions in Part 1 to identify a user via Google Client ID.
  • When linking to the external website pass through the Google Client ID (see part 1 for more details) as a URL parameter.
  • ?clientID=GA1.2.2129791670.1552388156
  • Initialise google analytics with the same Client ID.  This can also be achieved by setting the Client ID on the page load event in the GTM container.
  • function getUrlVars(){var n={};window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(r,e,i){n[e]=i});return n}
    ga('create', 'UA-XXXXX-Y', {
      'clientId': getUrlVars()["clientID"]
  • Inject the FXM beacon
  • Setup a custom Page Event called “updateGoogleCid” in Sitecore.
  • Hook up a custom FXM procesor that will merge the session.

The process above works for single page applications as well.

Trigger Goals

Out of the box triggering a goal is easily achieved by ‘page filter‘ or ‘capture a click action‘.

For single page applications, you can use the following API calls in your javascript.


Trigger Goals and Outcomes on Purchase

Out of the box triggering an outcome is achieved via a ‘capture a click action‘.

For the purposes of checkout, you are likely to want to see the dollar value purchased for that particular user in the Experience Profile. In order to achieve this, you need to use the javascript API to pass through the dollar value.  Be sure to create an outcome in Sitecore called ‘Purchase Outcome’.

SCBeacon.trackOutcome("Purchase Outcome", 
monetaryValue: revenue, 
xTransactionId: transactionId

A great tip that we received from the SBOS team in Australia was to trigger goals at checkout that had engagement value staggered according to the amount spent.

So, for example, you may have some javascript logic that looks like this:

if(revenue <= 99)
}else if(revenue >= 100 && revenue < 500)
}else if(revenue >= 500 && revenue < 1000)

For single page applications, you will need to use the javascript API.


Conclusion: In order to use FXM on any external website not built on Sitecore you need access to insert the Beacon code. If the external website is not a Single Page Application (also note some other limitations) you can use the FXM Experience Editor to achieve much of the desired functionality.

For those external websites containing Single page applications, ideally, you can also get access to either the GTM container or get the external website to insert some javascript for you. Using some clever javascript coding you can still record marketing events using the FXM javascript API. 

To continue reading jump over to Part 4, where we cover off a handy way to get personalisation working on the very first-page load.

Footnote: Single Page Applications

It’s important to note that out of the box FXM does not support single page applications. Look a bit further under the hood however and you will realise that FXM includes a great Javascript API.  After mentioning that you might now be thinking that if its a third party website you’re unlikely to get access to the source in order to implement any API calls.  At the end of the day, your going to need some sort access to inject FXM in order to achieve any sort of integration.

At the end of the day, your going to need some sort access to inject FXM in order to achieve any sort of integration.

This will likely place you in one of the following scenarios:

  1. Not a single page application, in which case you just need the external website to include the FXM beacon. (instructions)
    • This is by far the simplest scenario and happy days if your in this category.
  2. A single page application, with which you have access to make changes.
    • In this case, inject the FXM beacon on page load and use the Javascript API to trigger events, goals and outcomes.
  3. A single page application, with which you have no direct access to make changes, but can request changes to the GTM container.
    • In this case, a great backup is using the GTM container to inject the Beacon. You can then write custom javascript that uses javascript listeners to talk with the FXM API.
    • With some single page application frameworks (Angular, React, Vue) hooking into the existing javascript listeners will prove difficult. Your last remaining option may turn out to be inside the GTM container again. If the application is already sending back telemetry to Google Analytics, make good use of it. This could be achieved by either:
      • Writing a custom javascript snippet that looks for changes in Googles datalayer.
      • If events are configured directly in GTM, simply ask for changes to each event to include an FXM API call as well.
  4. If your unlucky and you have no access to make changes at all …. well …..
    •   shrug




Sitecore Geo-Spatial Results with Azure Search

This is the second blog post in this series. In the first blog post, I spoke about setting up Azure Search.

In this post, I am going to talk about using a Geo-Spatial lookup query that Azure Search has built in and the associated Index data type called Edm.GeographyPoint

The Azure Search overview can be found here and the configuration document here.

If you’re simply looking for a working example jump over to our GitHub repository that contains the full working helix site.

You can also find the latest version of the Sitecore package here:

Notice on the configuration document that it has a list of EDM data types and it also references the document from Microsoft which has the same list.

A notable exception is the that EDM.GeographyPoint is mentioned in the Microsoft document but not the Sitecore EDM list. So if you need to implement a geo-spatial lookup and get back results based on a latitude and longitude that isn’t possible with the Sitecore Azure Search provider out of the box.

If you dig a little deeper you can see that EDM.GeographyPoint is not in the search code. Have a look at the source code in Sitecore.ContentSearch.Azure.Schema.dll CloudSearchTypeMapper.GetNativeType()

We asked Sitecore support when and if EDM.GeographyPoint would be supported by the Sitecore provider and the answer was no plans in the immediate future.

The solution we came up with to solve this problem essentially involved two steps:

  • Getting data of type EDM.GeographyPoint into the Azure Search Index.
  • Performing a Spatial Query on the Azure Search Index (using geo.distance) in a timely manner.

To support both of the above operations we had to do a little digging into the core Sitecore Azure search code. Read on to find out about the solution we came up with or jump on over to the GitHub repository to look over the code.

Getting data into the index

As noted above the EDM.GeographyPoint data type simply isn’t coded into the Sitecore DLLs.  You can see this by looking at CloudSearchTypeMapper.GetNativeType() inside the DLL Sitecore.ContentSearch.Azure.Schema.dll.

If you’re still not convinced have a look in the Sitecore.ContentSearch.Azure.Http.dll


The next thing we tried was adding Edm.GeographyPoint as a field converter.

Once again we hit a brick wall with this solution as that doesn’t change the fact that Edm.GeographyPoint is not actually a known cloud type in the core DLLs.

If you look at the error message you get when attempting to add Edm.GeographyPoint as a Converter it tells us that CloudIndexFieldStorageValueFormatter.cs is attempting to look up the native EDM type.

Exception: System.NotSupportedException
Message: Not supported type: ‘Edm.GeographyPoint’
Source: Sitecore.ContentSearch.Azure at Sitecore.ContentSearch.Azure.Schema.CloudSearchTypeMapper.GetNativeType(String edmTypeName) at Sitecore.ContentSearch.Azure.Converters.CloudIndexFieldStorageValueFormatter.FormatValueForIndexStorage(Object value, String fieldName) at Sitecore.ContentSearch.Azure.CloudSearchDocumentBuilder.AddField(String fieldName, Object fieldValue, Boolean append)

Working around this limitation took a bit of trial and error but eventually, we nailed it down to the following steps:

  1. Created a new search configuration called spatialAzureIndexConfiguration.
  2. Create a new index configuration based on the new configuration in step 1.
  3. In the search configuration from step 1.
    • Add the following computed field.
    • <fields hint="raw:AddComputedIndexField">
       <field fieldName="geo_location" type="Aceik.Foundation.CloudSpatialSearch.IndexWrite.Computed.GeoLocationField, Aceik.Foundation.CloudSpatialSearch" />
    • Add a new cloud type mapper to map to EDM.GeographyPoint
    • <cloudTypeMapper ref="contentSearch/indexConfigurations/defaultCloudIndexConfiguration/cloudTypeMapper">
       <maps hint="raw:AddMap">
       <map type="Aceik.Foundation.CloudSpatialSearch.Models.GeoJson, Aceik.Foundation.CloudSpatialSearch" cloudType="Edm.GeographyPoint"/>
    • Replace the standard index field storage value formatter with the following the following class.
    • <indexFieldStorageValueFormatter type="Aceik.Foundation.CloudSpatialSearch.IndexWrite.Formatter.GeoCloudIndexFieldStorageValueFormatter, Aceik.Foundation.CloudSpatialSearch">
       <converters hint="raw:AddConverter">
  4. Our new formatter GeoCloudIndexFieldStorageValueFormatter.cs  inherits from the formatter in the core dlls and overrides the method FormatValueForIndexStorage. It detects if the field name contains “_geo” and basically prevents CloudSearchTypeMapper.GetNativeType from ever running and falling over.


The computed field GeoLocationField returns a GeoJson POCO which is serialised to the GeoJson format ( which is required for storage in Azure Search Index.

Getting data out of the index

The GitHub solution demonstrates two solutions.

Both solutions above at the end of the day allow you to perform an OData Expression query against your Azure Search Indexes. Using the “geo.distance” filter allows us to perform spatial queries using the computing power of the cloud.

The only downside is that search results returned don’t actually include the distance as a value. They are correctly filtered and ordered by the distance yet the actual distance value itself is not returned. We suggest voting for this change on this ticket :). For now, we suggest using the System.Device.Location.GeoCoordinate to figure out the distance between two coordinates.


In researching for possible solutions already out there we had a look at the following implementations for Lucene and SOLR:

  1. Sitecore Solr Spatial:
  2. Sitecore Lucene Spatial:

Special Thanks:

  • Ed Khoze –  For the research, he did and help with the initial prototype. Plus google address lookup advice.
  • Jose Dominguez – For getting the OData filtering query working.


Advanced System Reporter Custom Reports Part 3

Simple Custom Parameters Report

For this example, we will build a report which displays a set of results based on a Sitecore Query and custom parameters.


The report will display all items under a path selected by the user in the content tree and only return items of a template selected by the user.

Let’s build it

Step 1: Create a new filter called “Items of Type” and configure as per the image.


Step 2: Create a Parameter item called “TemplateID” and configure as per the image.


Note that the root id refers to the root templates folder you wish the user to be able to filter from.

Step 3: Create a report item called “Items by Template” and configure as per the image.


Step 4: Run the report from ASR. Here you can see we have run the report to return all articles within the categories node of the content tree.


Advanced System Reporter Custom Reports Part 2

Simple Sitecore Query based Report

For this example, we will build a report which displays a set of results based on a Sitecore Query.


The report will display all items under a fixed path in the content tree and only return items of predefined specific templates. The report query is fixed and doesn’t require any user input via report parameters.

Let’s build it

Step 1: Create a scanner called “Simple Sitecore Query” and add a query as per the image. The query below gets all items under the categories node where the template is one of the three listed types of templates.


Step 2: Create a new report called “Simple Sitecore Query Report” and configure as per the image.


Step 3: Run the report from ASR. Here you can see we have run the report to return all items of the specific templates within the categories node of the content tree.


Advanced System Reporter Custom Reports Part 1

Getting started

Firstly download the install package and the source code.

ASR reports are easy to configure and most of the time require no custom code to be written.

When a custom report requires coding the easiest way is to follow the examples found in the source code. There are also some filters; scanners etc. which are not in use by the default reports that are very handy and you would not know about them unless you have a browse through the ASR projects.



Report: The report item pulls together the configuration elements required for the ASR report: Scanners, Filters, Viewers, and Commands.
Viewer: The viewer item defines the report view, the columns to display etc.
Scanner: The scanner item defines the search query to populate the viewer.
Parameters: The parameter item defines a parameter input control to be used within a report. It defines what type of control and default values to display or select from depending on control type.
Parameter Type: The parameter type item defines a control type to be used by a parameter for a report. The default Parameter types, for example, are Date picker, Dropdown, Text etc.
Filters: The filter item adds filters to the scanner to provide filtered results for the report. Filters have an attribute field which maps a parameter item to a property within your filter class.
Command: The command item adds commands to the report ribbon. The commands can be used to execute functionality on items in the report.

All Items Scanner

The All items scanner is a great utility scanner. It references the QueryScanner which takes a Sitecore query from the attributes field. This allows you to create a report to output the results of any Sitecore query.