Turning on caching in Nuxt is one line:
routeRules: { '/products/**': { swr: 60 } }
TTFB drops from seconds to milliseconds. But a cached response is built for one request and then handed to everyone whose request maps to the same cache key. On a personalized site, that's how one visitor's data ends up in another visitor's browser.
I hit these while caching a large e-commerce storefront, then rebuilt each one in a minimal Nuxt 4.5.2 / Nitro 2.13.4 app to make sure they're real. Here are three you can test today.
1. A cookie set during rendering is replayed to everyone
A page that assigns an A/B bucket during SSR:
<script setup lang="ts">
const bucket = useCookie('bucket')
if (!bucket.value) bucket.value = 'b-' + Math.random().toString(36).slice(2, 8)
</script>
With swr: 60 on that route, three brand-new visitors got:
visitor 1 → set-cookie: bucket=b-ua93t6
visitor 2 → set-cookie: bucket=b-ua93t6
visitor 3 → set-cookie: bucket=b-ua93t6
The Set-Cookie header is stored with the cached response. Your whole experiment collapses into one bucket (or worse, if it's a session-like cookie).
The contrast that points to the fix: the same kind of cookie set in server middleware was not cached — each visitor got their own value, because middleware runs on every request before the cache. So: set per-visitor cookies in server/middleware, not in pages, components or Nuxt plugins, and only when the value actually changes.
2. Multi-domain sites share one cache entry
One app, several hosts (stores, tenants, de. / fr. domains). The page prints the host:
Host: store-a.test → host=localhost
Host: store-b.test → host=localhost ← whoever rendered first
The default key is path-based. The fix, verified in the same lab:
routeRules: {
'/': { swr: 60, cache: { varies: ['host', 'x-forwarded-host'] } },
}
Now each host gets its own entry, and repeat requests are still served from cache.
3. Per-visitor content is frozen into the HTML
The page reads a currency cookie during SSR:
Cookie: currency=EUR → currency=USD
Cookie: currency=PKR → currency=USD ← cached from the first, cookieless render
Fixes, in order: keep personalized UI out of SSR (<ClientOnly> / client fetch), render a neutral value and verify on the client, vary the key only for a small bounded set (never by a per-visitor cookie), or don't cache the route. And if prices also depend on experiments or segments, a currency check alone won't save you.
A 60-second test for your own site
Request a cached route twice as two new visitors and compare cookies one by one, not as a block — a fresh middleware cookie can hide a replayed one next to it:
curl -s -D - -o /dev/null https://staging.example.com/route | grep -i set-cookie
curl -s -D - -o /dev/null https://staging.example.com/route | grep -i set-cookie
# Any cookie with the SAME value for both requests is coming from the cache.
I wrote up all seven traps I've found (including ERR_HTTP_HEADERS_SENT during revalidation, state leaking across client navigation, per-process cache storage and measuring CLS the wrong way), with the lab app, a leak-test script and a Claude Code /cache-audit command that scans a Nuxt repo for these: Nuxt SSR Caching Without Leaks — code DEVTO takes 25% off.
Have you hit a caching leak I didn't list? I'd like to hear it in the comments.
Top comments (1)
the currency one is the commerce killer. a wrong price is not a cosmetic bug, it is a number the visitor cannot act on. the varies fix cuts both ways though: vary on currency everywhere and your hit rate quietly dies. a placeholder from the server with the real price resolved client-side is the safer default, and reserve key-varying for routes where currency actually changes the layout.