Stefan Holm Olsen

Performance tip: Don’t get a shopping cart until you need one

For those working on Optimizely Commerce sites, here is a simple performance tip.

Many web shops have a mini cart on all pages. Most commonly in the site header. On some sites it can be folded down to show the content. On other sites it is just a cart icon with a number (or maybe just a marker dot). This is fine and often provides a good user experience.

But on a few occasions, I have seen inefficient implementations, where the code would create a new empty cart for a session, when none could be found.

The code could look somewhat like this constructed example (cut out from a CMS 12 view component):

public IViewComponentResult Invoke()
{
// Loads the cart. Creates a new, if none is found.
var cart = _cartService.LoadOrCreateCart(_currentMarket.GetCurrentMarket());
bool cartHasItems = cart?.GetAllLineItems()?.Any() ?? false;

return View(cartHasItems)
}

In a particularly bad example, the mini cart controller even generated a full cart view model, only to check if it contained more than 0 items and then render a dot. Can you imagine?

What is wrong with it?

  1. The mini cart controller calls the order repository to load a cart for the current visitor (identified by the EPiServer_Commerce_AnonymousId cookie).
  2. If there is no such cart, a cart is created, cached and returned. There may also be custom initialization and validation logic there.
  3. We count the cart items and render it on the page.

This is not efficient, since we are potentially adding many unneeded empty shopping carts to the database.

What to do instead?

  1. Simply call LoadCart whenever needing to read a cart. If the cart is null, then we can safely return 0 as the number of cart items, anyway.
  2. When we do need to add something to the cart, call LoadOrCreateCart.

This is much more efficient, since we can now postpone creating a shopping cart until it is actually needed. So, a site with a lot of visitors, who never add anything to cart, will not affect the database as hard.

On a site that only needs to render a marker dot when the cart contains items, consider the following code in a CartViewComponent class:

public IViewComponentResult Invoke()
{
// Loads the cart. Returns null, if none is found. var cart = _cartService.LoadCart(_currentMarket.GetCurrentMarket()); bool cartHasItems = cart?.GetAllLineItems()?.Any() ?? false;
return View(cartHasItems) }

Note that the first call to LoadCart will cache the shopping cart or an empty placeholder (for a missing cart). So, we will not hit the database on every page view.

Bonus tip

Consider that search index bots generally do not hold on to issued session cookies.

This means that every request from a bot will issue a new session cookie. Our mini cart will therefore attempt to look up a non-existent cart and cache an empty placeholder for every request.

To keep it clean, we can completely skip loading a cart, when the request comes from a bot.

public static class HttpRequestExtensions
{
public static bool IsCrawlerRequest(this HttpRequest request)
{
string userAgent = request?.Headers["User-Agent"];

return userAgent != null && userAgent.Contains("bot", StringComparison.OrdinalIgnoreCase);
}
}

Then the previous CartViewComponent could be extended to this:

public IViewComponentResult Invoke()
{
// Don't waste any loading efforts on crawler requests.
var cart = Request.IsCrawlerRequest()
? null
: _cartService.LoadCart(_currentMarket.GetCurrentMarket());
bool cartHasItems = cart?.GetAllLineItems()?.Any() ?? false;

return View(cartHasItems)
}