pattern

Shell Personalisation

also called Cache the Shared, Personalise the Edge, Hole Punching

Generating and caching the page that is identical for everyone, then applying per-user variation at the edge or on the client - preserving the cache hit rate that personalising the whole page destroys.

myntravercelcachingrenderingpersonalisation

A page with one personalised element is frequently rendered per user, which discards the cache hit rate for the ninety-five percent of the page that is identical for everyone.

For a commerce or content platform, cache hit rate is a direct margin consideration — every miss is origin compute and origin egress — which makes the decision to personalise a whole page an expensive one taken for a small benefit.

Shell personalisation separates them: the shared page is generated once and cached; the personal elements are applied afterwards.

Why it matters

It reframes the question productively. Instead of "is this page personalised", the question becomes "which specific elements are personal" — and the answer is usually a small number that can be delivered separately.

Implementation patterns

  • Identify the genuinely personal elements: price for a logged-in customer, basket state, recommendations, entitlement, saved items. Everything else is shared.
  • Apply them at the edge where the input is a signed token or a cached lookup, or on the client where a brief delay is acceptable and the element is not part of the initial impression.
  • Stream the slow supplementary sections where server rendering is used, so the fast core appears immediately — removing the choice between a fast empty page and a slow complete one.
  • Keep URLs stable and free of variation, since query-string variation is a common silent cache-buster and it undoes the entire benefit.
  • Measure the cache hit rate as a first-class metric, since it is the number the pattern exists to protect and it degrades quietly when a new personalised element is added carelessly.

Industry example

Commerce platforms such as Myntra and Nykaa serve catalogue pages where the product content is identical for everyone and the price, availability and recommendations are not. The naive design renders per user and pays origin cost on every request; the separated design serves the shared page from the edge and applies the rest — and the difference at scale is a substantial share of the infrastructure bill.

Failure scenarios

  • Whole-page personalisation for one element, destroying the hit rate.
  • Cache-busting URL variation, which achieves the same outcome accidentally.
  • Personal data leaking into the cached shell, which is a correctness and privacy failure rather than a performance one — and the reason the separation must be enforced rather than conventional.
  • The personal elements loading so late that the page is visibly incomplete, which trades one problem for another.
  • Cache hit rate unmonitored, so degradation is discovered on the invoice.

Trade-offs

The page arrives in two phases, so a personal element appears after the shared content — which for a price is noticeable and can be jarring if the layout shifts. Reserving space for it is not optional, or the pattern trades performance for layout instability.

There is also a correctness hazard: a bug that caches a personalised response serves one user's data to another. That risk is why the separation must be structural — different cache keys, different code paths — rather than a convention that a future change can violate.

Interview question

"Your product page shows a personalised price. Take me through how you would still cache it, and tell me what would have to go wrong for one customer to see another's price."