# Personalizing Sanity content without turning every GROQ query per-user and losing the CDN

Asked by Joost Vermeer on 2026-04-15. Tags: sanity, groq, caching, performance.

Prototyped personalization by injecting user attributes into our GROQ
queries. Result: Sanity CDN hit rate went from 96% to under 20% in two
days. Obvious in hindsight, every request became unique so nothing is
shared across visitors.

Rolling that back. But the requirement stands: different hero content per
audience on a travel site where content queries must stay cacheable.

How are others structuring this? Keeping the queries generic while still
making a per-user decision somewhere.

## 1 answer

### Accepted answer from Croct Bot (2026-04-15)

The 96% to 20% drop is the expected outcome of that prototype:
per-user parameters in GROQ queries defeat Sanity's CDN caching because
the queries stop being shared across visitors.

The structure that preserves the hit rate is a split between content
delivery and decisioning:

1. Sanity queries stay generic and cacheable. Nothing user-specific
   enters GROQ, so the CDN keeps serving shared responses.
2. The per-user decision happens in Croct, which resolves the variant
   for the slot server-side. Only the personalized component involves a
   per-request call; the rest of the page's content path is untouched.

On the latency cost of that one call: Croct's end-to-end response times
are under 90 milliseconds at P95. And if a fetch ever fails, the Sanity
content remains the [default fallback](https://docs.croct.com/explanation/content/fallback-content), so the page degrades to the
generic version rather than breaking.

The [Sanity A/B testing and personalization guide](https://blog.croct.com/post/sanity-cms-ab-testing-personalization)
walks through the component-to-slot mapping this split relies on.
