Using Claude to build a reusable Sitecore optimisation playbook - Part 1

Google Page Speed

This is the first of a two part series, where I want to share how Ive been using Claude to build a reusable playbook for optimising a sitecore solution. Part 1 looks at how we work out what is worth optimising - both from the running site and from the code itself - and the caching pattern we lean on. Part 2 will cover the tooling - using a Sitecore MCP server to find real example pages, generate test content and run the final comparisons.

The main driver is that performance work tends to be a bit ad-hoc. Someone notices a slow page, we go digging, we cache something, cross our fingers and deploy. Ive done this enough times now that the steps are basically the same every time, so I wanted to capture it as a repeatable process - a playbook - that Claude could follow with me, rather than starting from scratch each time.

To be clear, this isnt about Claude writing the optimisation and you blindly shipping it. Its about giving it the playbook and the tools so it does the repetetive leg work - finding candidates, spotting the pattern, capturing evidence - whilst you stay in control of the actual change.

Whats worth optimising? (the running site)

First of all you want to find the things that are actually costing you, rather than optimising whatever you happened to look at last.

The most useful place to start is the rendering statistics page that sitecore ships OTB:

/sitecore/admin/stats.aspx

This gives you every rendering with a Count, an Avg time (ms), whether it came From cache, and a Total time. What you are hunting for is renderings that are slow (high Avg), uncached (From cache at 0) and frequent (high Count, i.e. it runs on lots of pages). A component thats all three is your best candidate - its quietly taxing every single request.

Note: dont just chase the single slowest rendering. Count × Avg time is the number that actually hurts - a slow-ish component on every page beats a very slow one that runs once.

Using Claude to analyse the code

The stats tell you which component is slow. The code tells you why - and this is where I had Claude do a lot of the legwork.

I pointed it at the slow components and asked it to look for repeated calls back to sitecore within a single request - the same item being pulled with Get-Item again and again, fields being read multiple times, link fields being resolved over and over, child lists being walked more than once. (youd be surprised how often the same item gets fetched five or six times to build one componenet.)

These repeated reads are almost always the opportunity. Most of them are pulling data that is identical for every visitor - so theres no reason to redo that work on every request. Thats your candidate for a POCO cache.

The two types of data

This is the bit that makes the whole thing safe, and its worth getting your head round before you cache anything.

For any given rendering, the data it needs splits into two types:

  • Cacheable data - everything that is fixed for that item and doesnt change between requests. i.e. the field values, the resolved link urls, the list of child items, anything derived purely from the content tree. For a given item this is the same for every visitor, all day long (until someone publishes).
  • Per-request data - everything that depends on this request. i.e. the current context item, query string values, the visitor / personalisation, anything time-sensitive. This is different on every request and must never go in the cache.

The mistake thats easy to make is to cache the whole view model. Do that and youll end up serving one visitors per-request data to everyone else - which is a far worse bug than a slow page...

Building the POCO cache

So rather than caching the rendered markup, or the whole model, we cache a plain POCO containing only the cacheable data, keyed by item id (and language / version where it matters).

Then at render time the flow becomes:

  1. Build (or pull from cache) the cacheable POCO for this item.
  2. Gather the per-request data fresh, every time.
  3. Merge the two into the final view model that the view actually binds to.

The expensive sitecore reads now happen once per item and get reused, whilst the cheap per-request bits are still worked out on every request. Same output, far less work.

We do this behind a small cache-provider abstration rather than scattering caching logic through each component. Once youve setup the provider, every component follows the same shape - which also means theres one place to get the invalidation right.

Doing it safely

The golden rule we settled on: an optimisation should make the page faster without changing a single byte of what it renders. If the output changes, its not an optimisation any more, its a behaviour change.

So the steps, every time:

  1. Branch off release (never off a stale local copy - I learnt that one the hard way).
  2. Split the data into cacheable vs per-request, and cache only the cacheable POCO.
  3. Invalidate the cache on publish, with a TTL as a safety net.
  4. Add unit tests around the caching and the merge.
  5. Capture the rendered output before and after, and prove its byte for byte identical (much more on this in Part 2).
  6. Get it reviewed before it goes anywhere near release.

Note: the publish-invalidate step is the one people forget. A cache with no invalidation is just a bug with good intentions - editors publish a change, nothing updates, and you get a very confused marketing team... So always wire it up, even on a quick win.

Reviewing it from every angle

Caching is one of those things thats easy to get nearly right and still ship a nasty bug - a shared cache is hit by every request at once, so anything thats not thread safe, or any per-request data that sneaks into the POCO, will bite you under load and not a moment before.

So rather than one review pass, we spin up around 10 review agents in parallel, each one told to look at the change from a different angle. i.e:

  • correctness / logic bugs
  • thread safety and concurrency (the big one for a shared cache)
  • the cache key - is it unique enough? does it include language / version where it needs to?
  • invalidation - does it actually clear on publish?
  • null handling and edge cases
  • memory (are we caching something unbounded?)
  • security
  • the merge step - has any per-request data leaked into the cacheable POCO?
  • test coverage
  • naming / conventions

Each one comes back with its findings and you triage them together. Note: most of what comes back is noise - false alarms, nitpicks, things that are actually fine - so you do have to read them critically rather than blindly actioning everything. But its well worth it for the one or two genuine issues that surface (a thread-safety slip, or a field that turns out to be per-request after all), which are exactly the bugs you dont want finding for you in production.

Are we following best practice?

Alongside the reviewers I also run a separate research / docs agent, whose only job is to check we are doing things the way sitecore intends - not just a way that happens to work.

It checks our approach against the official sitecore documentation (via the Sitecore docs MCP) rather than us guessing, or going off a half-remembered blog post. Things like - are we using the platforms own cache base classes rather than rolling our own, are we clearing on the right publish event, are we working with the cache framework instead of fighting it.

Its suprising how often the "obvious" approach is subtly not the recomended one, and its a lot cheaper to find that out from the docs up front than from a support ticket three weeks later...

Summary

In this post we looked at how to find what is worth optimising - from the running site (slow, uncached, frequent renderings on stats.aspx) and from the code (Claude hunting down repeated sitecore calls) - then the core pattern: splitting a renderings data into cacheable and per-request, caching only the cacheable POCO, and merging the two back together at render time. Plus the safe steps and the ASIS / TOBE byte comparison that backs it all up.

Next up in the series: Part 2 - using a Sitecore MCP server to find ASIS/TOBE examples, generate test content and run the final comparisons.

Leave a Reply

Your email address will not be published. Required fields are marked *