Implement Caching Strategies
Why implement caching
Section titled “Why implement caching”Every content request without caching hits the database, renders Razor templates, and builds the response from scratch. On high-traffic sites, this creates unnecessary load and slower page responses. CMS provides multiple caching layers — output caching, content repository caching, and custom cache dependencies — that work together to serve cached responses until content changes.
What you will do
Section titled “What you will do”- Enable output caching on page controllers
- Configure content cache settings
- Set up cache dependencies for automatic invalidation
- Build a custom cache key strategy
Enable output caching on controllers
Section titled “Enable output caching on controllers”Use the [OutputCache] attribute on your page controllers to cache the full HTTP response.
using Microsoft.AspNetCore.Mvc;
using Optimizely.Cms.Core.Web;
namespace MySite.Controllers;
public class ArticlePageController : PageController<ArticlePage>
{
[ResponseCache(
Duration = 300,
Location = ResponseCacheLocation.Any,
VaryByQueryKeys = new[] { "page", "category" })]
public IActionResult Index(ArticlePage currentPage)
{
return View(currentPage);
}
} Key settings:
Duration— Cache lifetime in seconds. Start with 300 (5 minutes) and adjust.Location— UseAnyfor CDN and browser caching,Clientfor browser only.VaryByQueryKeys— Create separate cache entries for different query parameters.
Verify: Open browser devtools, navigate to a cached page, and confirm the Cache-Control response header shows your configured max-age. Reload the page and check for a 200 (from disk cache) or 200 (from memory cache) status in the Network tab.
Configure CMS content caching
Section titled “Configure CMS content caching”The CMS caches content objects in memory by default. You can tune this behavior in Program.cs.
using Optimizely.Cms.Core;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCms()
.Configure<ContentCacheOptions>(options =>
{
// Maximum items in the content cache
options.CacheSize = 5000;
// Time before cache entries expire
options.SlidingExpiration =
TimeSpan.FromMinutes(20);
// Enable cache statistics for monitoring
options.EnableStatistics = true;
}); The content cache stores deserialized IContent objects. Increasing CacheSize uses more memory but reduces database queries.
Verify: After configuring content caching, enable EnableStatistics and check the cache hit ratio in Application Insights or your custom telemetry dashboard. A healthy content cache should show a hit ratio above 85%. If the ratio is lower, increase CacheSize or review your cache expiration settings.
Set up cache dependencies
Section titled “Set up cache dependencies”Cache dependencies ensure that when content changes, all related cached responses are automatically invalidated.
using Microsoft.AspNetCore.Mvc;
using Optimizely.Cms.Core.Web;
using Optimizely.Cms.Core.Caching;
namespace MySite.Controllers;
public class StartPageController : PageController<StartPage>
{
private readonly IContentCacheKeyCreator _cacheKeys;
public StartPageController(
IContentCacheKeyCreator cacheKeys)
{
_cacheKeys = cacheKeys;
}
public IActionResult Index(StartPage currentPage)
{
// Create cache dependency on the start page
// When the start page is republished,
// this cached response is purged automatically
var dependency = _cacheKeys
.CreateCommonCacheKey(currentPage.ContentLink);
HttpContext.Response.Headers.Append(
"X-Cache-Dependency", dependency);
return View(currentPage);
}
} Build a custom cache key strategy
Section titled “Build a custom cache key strategy”For pages that vary by user state or custom conditions, implement a cache key strategy.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MySite.Caching;
public class VaryByUserTypeAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(
ResultExecutingContext context)
{
var isAuthenticated = context.HttpContext.User
.Identity?.IsAuthenticated ?? false;
var cacheProfile = isAuthenticated
? "authenticated"
: "anonymous";
context.HttpContext.Response.Headers.Append(
"Vary", "X-User-Type");
context.HttpContext.Items["CacheProfile"] =
cacheProfile;
base.OnResultExecuting(context);
}
} Apply the attribute to controllers that serve different content for logged-in and anonymous users.
Note: The
VaryByUserTypeAttributesets aVaryheader and stores a cache profile identifier, but it does not invoke the response caching middleware on its own. To make this work, you must also register response caching inProgram.cs(builder.Services.AddResponseCaching()andapp.UseResponseCaching()) and apply[ResponseCache]on the controller action alongside[VaryByUserType]. WithoutUseResponseCaching()in the pipeline, theVaryheader is sent to downstream caches (CDN, browser) but the server does not cache the response itself.
Verify: After applying the attribute, make a request as an anonymous user and check the Vary response header in devtools — it should include X-User-Type. Then log in and repeat. Confirm the two requests produce different cached entries by comparing response content or checking the X-Cache-Profile value if you expose it.
Disable caching for edit mode
Section titled “Disable caching for edit mode”Never cache responses when editors are previewing content. The CMS sets a context flag you can check.
using Optimizely.Cms.Core.Web;
public IActionResult Index(ArticlePage currentPage)
{
if (PageEditing.PageIsInEditMode)
{
Response.Headers.CacheControl = "no-cache, no-store";
}
return View(currentPage);
} Multi-instance cache consistency
Section titled “Multi-instance cache consistency”On PaaS hosting, each web application instance maintains its own in-memory content cache. When content is published, the CMS invalidates the cache on the instance that processed the publish event, but other instances may continue serving stale content until their cache entries expire or they receive a cache invalidation message.
Approaches:
- Accept eventual consistency — If your
SlidingExpirationis short (5-20 minutes), stale content resolves itself quickly. This is acceptable for most content sites where a few minutes of delay after publishing is tolerable. - Use a distributed cache — Replace the default in-memory cache with a distributed cache provider like Redis. Configure this in
Program.csby registering anIDistributedCacheimplementation. This ensures all instances share the same cache state, but adds a network hop for every cache read. - Enable cache invalidation messaging — Optimizely CMS supports inter-instance cache invalidation through Azure Service Bus or a message broker. When configured, a publish event on one instance sends an invalidation message to all others. This is the recommended approach for multi-instance PaaS deployments where freshness matters.
For single-instance deployments or SaaS, this is not a concern — the platform handles cache consistency automatically.
Monitoring cache performance
Section titled “Monitoring cache performance”| Metric | Where to check | Target |
|---|---|---|
| Cache hit ratio | Application Insights or custom telemetry | Above 85% for content pages |
| Memory usage | Process metrics | Keep content cache under 500 MB |
| Cache evictions | EnableStatistics counters | Increase CacheSize if evictions are high |
| Response time | Request duration metrics | Under 100 ms for cached pages |
1. Your CMS PaaS site runs on three web application instances behind a load balancer. After publishing a content update, two of the three instances continue serving stale content for several minutes. What is the recommended solution?
Inter-instance cache invalidation messaging (via Azure Service Bus or a message broker) is the recommended approach for multi-instance PaaS deployments. When content is published on one instance, invalidation messages ensure all other instances clear their stale cache entries.
Inter-instance cache invalidation messaging (via Azure Service Bus or a message broker) is the recommended approach for multi-instance PaaS deployments. When content is published on one instance, invalidation messages ensure all other instances clear their stale cache entries.
Review this topic →2. Editors report that changes they make in the CMS are not visible when they preview pages. Investigation reveals that output caching is returning cached responses even in edit mode. How should you fix this?
The CMS provides a PageIsInEditMode flag that your controllers should check. When editors are previewing content, responses must not be cached — set Cache-Control to 'no-cache, no-store' to ensure editors always see fresh content.
The CMS provides a PageIsInEditMode flag that your controllers should check. When editors are previewing content, responses must not be cached — set Cache-Control to 'no-cache, no-store' to ensure editors always see fresh content.
Review this topic →3. Your content cache hit ratio is at 60%, well below the recommended 85% target. The site has 10,000 content items but the cache is configured for a maximum of 2,000 items. What is the most effective change?
A low cache hit ratio combined with a CacheSize smaller than the content item count suggests frequent cache evictions. Increasing CacheSize allows more items to stay in memory, directly improving the hit ratio (at the cost of more memory usage).
A low cache hit ratio combined with a CacheSize smaller than the content item count suggests frequent cache evictions. Increasing CacheSize allows more items to stay in memory, directly improving the hit ratio (at the cost of more memory usage).
Review this topic →