Understanding Nuxt Caching and Edge Strategies
AI generated
<v/>
{ }
Nuxt 3 · Caching · Edge Deployment · Nitro · Performance · CDN
Understanding Nuxt Caching
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.

20 min read Nitro Cache · ISR · routeRules · Cache-Control · Stale-While-Revalidate · Edge Nuxt 3 · Cloudflare · Vercel · Node.js

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.

11. FAQ: Nuxt Caching and Edge Strategies

1Nitro cache vs. CDN cache, what is the difference?
Nitro cache: server-side, avoids DB access. CDN cache: upstream, the Nuxt server is not contacted. Both independently configurable and complementary.
2ISR vs. pure SSG?
SSG: all pages once at build time. ISR: on demand on the first request, revalidation in the background. ISR scales to thousands of pages without a build-time explosion.
3Protecting personalized pages from CDN caching?
Set Cache-Control: private, no-store. routeRules: cache: false. No ISR for personalized routes. The CDN respects these headers.
4Invalidating the Nitro cache programmatically?
useStorage('cache').removeItem(key), the key matches the getKey() value. The next request triggers a fresh fetch and cache entry.
5Limitations of edge deployment?
No Node.js API access (fs, path, native modules). Libraries must be edge-compatible. Persistent cache requires a KV store or Redis.
6Why is getKey so important in the Nitro cache?
Without getKey: all requests under the same key, user A sees user B's response. getKey must include all request variables (query params, locale).
7Configuring Redis as Nitro storage?
nitro.storage.redis.driver: 'redis', url: REDIS_URL in nuxt.config. In the handler: base: 'redis'. Automatic TTL matching maxAge, no manual cleanup.
8What is stale-while-revalidate?
Serve the cached (stale) response immediately, start revalidation in the background. No waiting. Sensible when short staleness windows are tolerable.
9Measuring cache hit rate?
Nitro: logging whether the handler is reached. CDN: check the CF-Cache-Status header. Cloudflare Analytics, Vercel Analytics for edge monitoring.
10Caching strategy for e-commerce?
Homepage and categories: ISR 5 to 15 min. Product pages: ISR 2 to 5 min plus webhook invalidation. Checkout/account: SSR, Cache-Control: private. API: Nitro cache plus CDN public.