Nuxt 3/Nitro Cache: Unable to invalidate cache in production

  • I have an API route /api/text-snippet/en which makes an API call to the database to fetch all text snippets
  • The admin is able to edit the text snippets and he can manually delete the text snippet cache with a click of a button

Issue: It’s not working in a Vercel environment only, it’s working locally.
How I define the cached function in Nuxt: /api/text-snippet/en:

export default defineCachedEventHandler(async (event) => {
    const { locale } = getRouterParams(event)

    const supabase = serverSupabaseServiceRole<Database>(event)

    const { data, error } = await supabase.from('text_snippet')
        .select('*')
        .eq('locale', locale)

    if (error) throw createError(error)

    return data
}, {
    swr: true,
    maxAge: 86400,
    staleMaxAge: 172800,
    getKey: (event) => `text-snippet:${getRouterParams(event).locale}`
})

Code to invalidate the cache: /api/admin/cache/invalidate.post.ts

export default defineEventHandler(async (event) => {
    try {
        const cacheStorage = useStorage('cache')

        await cacheStorage.clear()

        return { success: true }
    } catch (error: any) {
        throw createError(error)
    }
})

These are the response headers:

age:
1560

cache-control:
public, max-age=0, must-revalidate

content-encoding:
br

content-type:
application/json

date:

Thu, 30 Jan 2025 10:03:39 GMT

last-modified:
Thu, 30 Jan 2025 10:03:37 GMT

server:
Vercel

strict-transport-security:
max-age=63072000

x-vercel-cache:
HIT

x-vercel-id:
fra1::iad1::8mvcg-1738232980359-72dbc3df8dd6
  • Nuxt 3
  • Production environment

Hi, Phillip!

To resolve the cache invalidation issue, could you try to first add cache control headers to your API routes, setting ‘Cache-Control’, ‘Pragma’, and ‘Expires’ headers to prevent Vercel from caching responses.

Next, modify the cache invalidation endpoint to not only clear the Nitro cache as before but also include the same cache control headers, ensuring Vercel doesn’t cache the invalidation response.

Then, update your nuxt.config.ts file to add route rules that set default cache headers for all API routes.

These changes work in concert to prevent Vercel’s Edge Cache from interfering with Nitro’s caching mechanism, ensure proper cache invalidation in the production environment, and maintain your desired Nitro caching behavior.

These changes work together to:

  • Prevent Vercel’s Edge Cache from interfering with Nitro’s caching mechanism.
  • Ensure that cache invalidation works correctly in the production environment.
  • Maintain the desired caching behavior you’ve set up with Nitro.

Let us know how you get on!

Hi Pauline, thanks for your quick reply. I’ve done what you suggested but unfortunately I’m still getting x-vercel-cache: HIT

This is how I’ve implemented it:

invalidate.post.ts

export default defineEventHandler(async (event) => {
    try {
        // disable Vercel cache for this route
        setResponseHeaders(event, {
            'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        });
        const cacheStorage = useStorage('cache')

        await cacheStorage.clear()

        return { success: true }
    } catch (error: any) {
        throw createError(error)
    }
})

api/text-snippet/[locale].ts

import { serverSupabaseServiceRole } from '#supabase/server'
import { Database } from '~/types/supabase-generated.types'

export default defineCachedEventHandler(async (event) => {
    // disable Vercel cache for this route
    setResponseHeaders(event, {
        'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
        'Pragma': 'no-cache',
        'Expires': '0'
    });
    const { locale } = getRouterParams(event)

    const supabase = serverSupabaseServiceRole<Database>(event)

    const { data, error } = await supabase.from('text_snippet')
        .select('*')
        .eq('locale', locale)

    if (error) throw createError(error)

    return data
}, {
    swr: true,
    maxAge: 86400,
    staleMaxAge: 172800,
    getKey: (event) => `text-snippet:${getRouterParams(event).locale}`
})

nuxt.config.ts

routeRules: {
      '/api/text-snippet/**': {
        cache: {
            swr: true,
            maxAge: 86400,
            staleMaxAge: 172800,
            name: 'text-snippet',
        },
        headers: {
            'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        }
      }
  },

I can see the headers correctly in the API request. See screenshot attached

I’m relatively sure it’s because of the Vercel Data Cache. Because whenever I’ve deployed to the server the cache was purged and I got the new data.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.