and Edge Strategies, from Nitro to the CDN Edge
Nuxt 3 with Nitro offers several caching layers that can be configured independently of each other: server-side Nitro cache, ISR with stale-while-revalidate, CDN cache-control headers and edge deployment. The right combination of these layers makes the difference between an application with an 800 ms TTFB and one with 40 ms.
Table of Contents
- 1. The caching layers in Nuxt 3
- 2. Nitro cache: server-side caching for server routes
- 3. routeRules: configuring caching at the page level
- 4. ISR in Nuxt 3: implementing Incremental Static Regeneration correctly
- 5. Cache-Control headers: controlling CDN caching
- 6. Cache invalidation: when and how to remove cache entries
- 7. Edge deployment with Nitro: Cloudflare Workers and Vercel Edge
- 8. Nitro storage: distributed caching with Redis and KV stores
- 9. Nuxt caching strategies compared
- 10. Summary
- 11. FAQ
1. The caching layers in Nuxt 3
The Nuxt caching model consists of several independent layers, each covering a different part of the request/response cycle. The first and deepest layer is the Nitro cache: server-side caching of server route responses, in memory or in a configured storage backend. The second layer is CDN cache-control headers, which tell the upstream CDN how long it is allowed to store a response. The third layer is ISR (Incremental Static Regeneration), which combines statically pre-rendered HTML pages with a configurable revalidation time. The fourth layer is browser caching via HTTP headers.
These layers can be configured independently, but they interact. A server route with a Nitro cache of 60 seconds that sets a Cache-Control: public, max-age=3600 header will be cached by the CDN for an hour, regardless of the Nitro cache expiry. That is intentional if the data really does change rarely, and problematic if the Nitro cache has fresh data but the CDN still has a stale copy. Understanding this interaction is the key to a correct Nuxt caching strategy that neither serves outdated data nor wastes performance potential.
The first step in planning a Nuxt edge strategy is therefore a classification of content types: how often does this data change? Is it user-specific (personalized) or public? Can stale data be tolerated for a certain time? These questions determine which caching layer(s) make sense for each content type. Personalized data must never end up in a shared CDN cache. Public data that changes rarely is ideal for aggressive caching strategies across all layers.
2. Nitro cache: server-side caching for server routes
The Nitro cache is the most powerful and flexible caching layer in the Nuxt 3 architecture. Using defineCachedEventHandler instead of defineEventHandler caches a server route. The cache key, the cache duration and the cache storage backend are configurable per route. By default Nitro caches in memory, which is fast but not shared across multiple server instances. With an external storage backend such as Redis, the cache becomes distributed and survives server restarts.
The getKey function in the Nitro cache configuration object is crucial for cache granularity. A server route without getKey caches all requests under the same key, which is problematic for routes that process query parameters or locale headers. A getKey function that includes query parameters, Accept-Language headers or user segments creates granular cache entries that return correct responses for different request variants. That is the difference between a correct cache and a broken one.
// server/api/products/featured.get.ts
// Nitro cached route, respects locale and currency query params in cache key
export default defineCachedEventHandler(async (event) => {
const query = getQuery(event)
const locale = (query.locale as string) ?? 'de'
const currency = (query.currency as string) ?? 'EUR'
// Fetch from external API or DB, result will be cached per locale+currency
const products = await $fetch(`https://api.example.com/products/featured`, {
headers: { 'Accept-Language': locale },
query: { currency },
})
// Set CDN cache headers in addition to Nitro cache
setResponseHeaders(event, {
'Cache-Control': 'public, max-age=300, stale-while-revalidate=60',
})
return products
}, {
maxAge: 60 * 5, // 5 minutes in Nitro cache
// Cache key includes locale and currency, separate entry per combination
getKey: (event) => {
const q = getQuery(event)
return `featured:${q.locale ?? 'de'}:${q.currency ?? 'EUR'}`
},
// Store in Redis for distributed caching across multiple server instances
base: 'redis',
})
3. routeRules: configuring caching at the page level
routeRules in nuxt.config.ts is the central tool for page-level Nuxt caching. It lets you define an individual rendering and caching strategy for each URL pattern, without having to manually configure every single page. That makes routeRules the ideal tool for hybrid applications where different page types have different caching requirements.
The most important routeRules options for Nuxt caching: prerender: true renders the page once at build time into a static HTML file. isr: N implements ISR with a revalidation time of N seconds, the page is rendered on the first request, cached, and revalidated in the background on the next request after N seconds. cache: { maxAge: N } caches the SSR response for N seconds in the Nitro cache. ssr: false switches to client-only rendering, no server-side rendering, no caching on the server side.
// nuxt.config.ts, route-level caching strategy per URL pattern
export default defineNuxtConfig({
routeRules: {
// Marketing pages: pre-rendered once at build time, CDN serves static HTML
'/': { prerender: true },
'/about': { prerender: true },
'/blog': { prerender: true },
// Blog posts: ISR, rendered on first request, revalidated every 10 minutes
'/blog/**': { isr: 600 },
// Product pages: ISR with shorter interval for fresher data
'/products/**': { isr: 120 },
// API routes: Nitro-cached responses, CDN-cacheable
'/api/catalog/**': { cache: { maxAge: 300 } },
// User-specific pages: SSR only, no caching (personalized content)
'/account/**': { ssr: true, cache: false },
// Admin: SPA mode, no SSR, no server cache
'/admin/**': { ssr: false },
},
})
4. ISR in Nuxt 3: implementing Incremental Static Regeneration correctly
Incremental Static Regeneration (ISR) is the caching pattern that offers the best compromise between static performance and dynamic freshness. The principle: the first request for a URL triggers a server render, and the result is cached. Every following request within the revalidation window gets the cached version, at server speed, without a re-render. Once the revalidation time has elapsed, the next request is served the cached version (stale-while-revalidate), while a new render is started in the background. The next request after that gets the fresh version.
The Nuxt ISR mechanism via routeRules: { '/products/**': { isr: 300 } } implements exactly this stale-while-revalidate pattern. The user never waits for a render, they always get a cached version. The maximum "staleness" is bounded by the ISR value. For product catalogs, blog pages and other content that changes occasionally but does not require real-time freshness, ISR is the optimal caching strategy: fast like static, current like SSR, without the infrastructure complexity of manual cache busting.
5. Cache-Control headers: controlling CDN caching
HTTP Cache-Control headers are the interface between the Nuxt 3 server and upstream CDNs such as Cloudflare, AWS CloudFront or Fastly. Correct headers allow the CDN to cache responses and serve them directly from the edge network, without contacting the origin server. That drastically reduces server load and latency for geographically distributed users.
The most important Cache-Control directives for Nuxt edge strategies: public allows CDN caching of responses (without it, the CDN does not cache by default). max-age=N defines the cache lifetime in seconds from the CDN's perspective. stale-while-revalidate=M allows serving stale responses for M seconds while revalidating in the background. s-maxage=N defines the cache lifetime only for CDNs (overrides max-age for shared caches). no-store prevents any caching whatsoever, mandatory for personalized and sensitive responses.
6. Cache invalidation: when and how to remove cache entries
Cache invalidation is the hardest problem in Nuxt caching, not technically, but conceptually. The question "when should which cache entry become invalid?" has no simple universal answer. It depends on the data dependencies: when a product is updated, the Nitro cache for /api/products/[id], the ISR-cached /products/[id] page and potentially the CDN-cached response all need to be invalidated. Those are three different systems with three different invalidation mechanisms.
In the Nitro cache, a single cache entry can be removed programmatically: useStorage('cache').removeItem(key) removes the entry, and the next request triggers a fresh fetch. For the CDN cache, invalidation via the CDN API is required, the Cloudflare Cache Purge API, Vercel Revalidation Token or AWS CloudFront Invalidation. These CDN invalidations can be triggered from a Nuxt server route that is called by a webhook from the content management system. The pattern: CMS changes content, webhook fires, Nuxt route invalidates Nitro cache and CDN cache, and the next page view gets fresh content.
// server/api/_invalidate.post.ts
// Webhook endpoint, called by CMS when content changes
// Protected by shared secret to prevent unauthorized cache clears
export default defineEventHandler(async (event) => {
// Verify webhook signature
const secret = getHeader(event, 'x-webhook-secret')
if (secret !== process.env.WEBHOOK_SECRET) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
const body = await readBody(event)
const { type, id } = body // e.g. { type: 'product', id: 'abc-123' }
if (type === 'product') {
// 1. Invalidate Nitro cache for this product's API route
const storage = useStorage('cache')
await storage.removeItem(`nitro:handlers:/api/products/${id}:product:${id}`)
// 2. Trigger ISR revalidation for the product page
// Nuxt ISR: setting isr: 0 in routeRules for a request forces fresh render
await $fetch(`/products/${id}`, {
headers: { 'x-nuxt-revalidate': process.env.REVALIDATE_SECRET ?? '' }
})
// 3. Purge CDN cache (Cloudflare example)
await $fetch(`https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE}/purge_cache`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CF_TOKEN}` },
body: { files: [`https://example.com/products/${id}`] },
})
}
return { success: true, invalidated: `${type}:${id}` }
})
7. Edge deployment with Nitro: Cloudflare Workers and Vercel Edge
Nuxt edge deployment with Nitro means running the Nuxt application not on a central server, but on edge nodes worldwide, geographically close to the users. Cloudflare Workers, Vercel Edge Functions and similar services execute JavaScript directly at the CDN PoP. The time to first byte drops from 200 to 800 ms (origin request) down to 10 to 50 ms (edge request), because no geographical detour to the origin server is required.
The Nitro preset for Cloudflare Workers (nitro: { preset: 'cloudflare-workers' }) compiles the entire server code into a single worker script that does not rely on Node.js APIs in V8. That is an important constraint: Node.js-specific APIs (fs, path, native modules) do not work in the edge runtime. Libraries have to be edge-compatible. This is the most common reason edge deployments fail, dependencies that internally use Node.js APIs. The solution: check edge compatibility of the npm package or switch server middleware to edge-compatible alternatives.
8. Nitro storage: distributed caching with Redis and KV stores
Nitro storage is the abstract interface that connects the Nitro cache to various backend storages. In development, in-memory storage is sufficient. In production, with multiple server instances or edge deployment, a distributed storage backend is needed: Redis for classic server deployment, Cloudflare KV or Vercel KV for edge deployments. The configuration in nuxt.config.ts via nitro.storage is consistent, the application code does not change, only the backend configuration.
Redis as a Nitro storage backend offers three decisive advantages over an in-memory cache: the cache survives server restarts and deployments. Multiple server instances share the same cache, eliminating cold-start problems during horizontal scaling. Redis naturally supports TTL, so Nitro cache entries expire automatically without any manual cleanup logic. The latency to a local Redis server (under 1 ms) is negligible compared to the CPU cost of an SSR render. The ratio of cache hit rate to rendering overhead makes Redis-backed Nitro cache particularly economical for high-traffic SSR pages.
9. Nuxt caching strategies compared
Choosing the right Nuxt caching strategy depends on content type, update frequency and personalization requirements. The following table shows the most important strategies and their optimal use cases.
| Strategy | Configuration | Optimal use case | TTFB expectation |
|---|---|---|---|
| Static pre-rendering | prerender: true | Docs, landing pages, blog (rarely changed) | ~20 ms (CDN edge) |
| ISR / SWR | isr: 300 | Product, news and blog pages with regular updates | ~30 to 80 ms (cached) |
| Nitro cache SSR | cache: { maxAge: 60 } | Public pages with medium update frequency | ~50 to 150 ms (server) |
| SSR without cache | cache: false | Personalized pages, checkout, profile | ~300 to 800 ms (origin) |
| Client-only (SPA) | ssr: false | Admin, dashboards, auth-protected UIs | Shell instantly, data via API |
The most important optimization in practice: push static and cached pages as far forward as possible, and use SSR without cache only for content that has to change in real time or is user-specific. A product page that is identical for all users does not need full SSR without cache, ISR with a 5-minute interval is 10 times faster and reduces the load on the origin server by 99 percent. Making this decision per page type is the single biggest performance lever in a Nuxt caching strategy.
Mironsoft
Nuxt 3 performance, caching strategies and edge deployment architecture
Nuxt 3 applications that are fast, measurably and reproducibly?
We analyze TTFB, cache hit rates and CDN configurations in existing Nuxt projects and implement Nitro caching, ISR and edge strategies that show the difference in real measurements.
Caching audit
TTFB analysis, cache hit rate measurement and identification of caching gaps
routeRules strategy
Configuring and testing ISR, Nitro cache and CDN headers per page type
Edge deployment
Nitro presets for Cloudflare Workers or Vercel Edge, including a compatibility audit
10. Summary
The Nuxt caching model offers several independent layers: Nitro cache for server route responses, routeRules for page-level caching, ISR for stale-while-revalidate behavior, Cache-Control headers for CDN control, and edge deployment for geographically minimized latency. The right combination depends on content classification: public versus personalized, static versus dynamic, frequent versus rare changes.
The biggest performance gains come from consistently applying the caching hierarchy: static pre-rendering wherever possible, ISR wherever freshness matters, and SSR without cache only where personalized real-time data is genuinely required. Cache invalidation via webhooks and programmatic Nitro storage access enables manual invalidation on content updates without having to wait out cache timeouts. Edge deployment with Nitro presets for Cloudflare or Vercel reduces TTFB for global users to milliseconds, the single biggest performance lever for internationally used Nuxt edge applications.
Nuxt caching and edge strategies, the essentials at a glance
Nitro cache
defineCachedEventHandler with maxAge and a granular getKey. Redis backend for a distributed cache across multiple server instances.
ISR / stale-while-revalidate
routeRules: { '/products/**': { isr: 300 } }, cached version instantly, revalidation in the background. Ideal for product and blog pages.
Cache invalidation
Webhook to server route to removing the Nitro storage entry plus CDN purge API. CMS-driven invalidation without timeout waits.
Edge deployment
nitro.preset: 'cloudflare-workers' or 'vercel-edge'. No Node.js API access, check edge compatibility of all dependencies.