Appearance
Cache Tags
Cache tags allow you to easily invalidate one or more "related" items across caches.
Basics
A cache tag is a unique identifier of something like a page, an image or any context:
article:235
language:de
image:313
config:site:page_title
image_style:hero_large_retina
Typically, cache tags are provided by a backend (such as a CMS), but they can also be managed directly by your app.
If cache tags are provided by a backend it's likelay also the backend that will automatically invalidate the appropriate cache tags when needed. nuxt-multi-cache offers an API endpoint to purge one or more cache tags.
Integration
The component cache, data cache and route cache provide a way to store cache tags along the cached item:
Component Cache
You can either provide cache tags on the <RenderCacheable>
component:
vue
<template>
<div>
<RenderCacheable :cache-tags="['weather']">
<Weather />
</RenderCacheable>
</div>
</template>
or by using the useComponentCache composable:
typescript
useComponentCache((helper) => {
helper.addTags(['weather'])
})
Data Cache
Cache tags can be added via the addToCache
method returned by useDataCache:
typescript
const { value, addToCache } = await useDataCache<WeatherResponse>('weather')
if (value) {
return value
}
const response = await $fetch<WeatherResponse>('/api/getWeather')
await addToCache(response, ['weather'])
Route Cache
You can add cache tags using the useRouteCache composable:
typescript
useRouteCache((helper) => {
helper.addTags(['weather'])
})
Purging
The API offers an endpoint to purge items by cache tag.
Using the three examples from above, you can invalidate the cached data, component and route all at once:
sh
curl -X POST -i \
-H "Content-Type: application/json" \
-H "x-nuxt-multi-cache-token: hunter2" \
--data '["weather"]' \
http://localhost:3000/__nuxt_multi_cache/purge/tags
By default, cache tags are not invalidated immediately, but after a fixed delay that can be configured via api.cacheTagInvalidationDelay in the module options.
After the delay, all three items are removed from the cache.
Cache Tag Registry
By default, in order to "know" which items to purge from the cache when tags are invalidated, the module iterates over all cache items and parses them to be able to match their cache tags with the tags to invalidate. While this might work for small to medium amount of cached items, it does not scale for large applications with potentially thousands of cache items and tags.
The job of the Cache Tag Registry is to provide a fast and efficient way to look up which cache keys to delete when one or more cache tags are invalidated.
Using the built-in in-memory registry
You can enable the built-in cache tag registry in your server options file:
typescript
import { defineMultiCacheOptions } from 'nuxt-multi-cache/server-options'
export default defineMultiCacheOptions(() => {
return {
cacheTagRegistry: 'in-memory',
}
})
External Caches
The in-memory cache tag registry only works when all cache storages are also in-memory! If you use redis, valkey or any other external cache driver, it will only work as long as both your app and the cache backend are in sync.
If you restart your app without also purging the external cache, your app will not have any knowledge about the cache tags of your (still cached) items.
Multiple Instances
When you run your app in multiple instances (e.g. via PM2 Cluster Mode), keep in mind that each instance will track its own cache items and tags; purging via the API will only affect a random instance.
Custom Implementation
Provide an object in cacheTagRegistry
that implements the CacheTagRegistry
interface.
You can use the built-in in-memory registry implementation as a reference:
ts
import type { CacheType } from '../types'
import type { CacheTagRegistry } from '../types/CacheTagRegistry'
const CACHE_TYPES: CacheType[] = ['route', 'data', 'component']
export class InMemoryCacheTagRegistry implements CacheTagRegistry {
private tagToKeys = new Map<CacheType, Map<string, Set<string>>>(
CACHE_TYPES.map((type) => [type, new Map()]),
)
private keyToTags = new Map<CacheType, Map<string, Set<string>>>(
CACHE_TYPES.map((type) => [type, new Map()]),
)
getCacheKeysForTags(
tags: string[],
): Promise<Partial<Record<CacheType, string[]>>> {
const result: Partial<Record<CacheType, string[]>> = {}
for (const cacheType of CACHE_TYPES) {
const keysSet = new Set<string>()
const tagMap = this.tagToKeys.get(cacheType)!
for (const tag of tags) {
const keys = tagMap.get(tag)
if (keys) {
for (const key of keys) {
keysSet.add(key)
}
}
}
if (keysSet.size) {
result[cacheType] = Array.from(keysSet)
}
}
return Promise.resolve(result)
}
removeTags(tags: string[]): Promise<void> {
for (const cacheType of CACHE_TYPES) {
const tagMap = this.tagToKeys.get(cacheType)!
const keyMap = this.keyToTags.get(cacheType)!
for (const tag of tags) {
const keys = tagMap.get(tag)
if (!keys) {
continue
}
for (const key of keys) {
const keyTags = keyMap.get(key)!
keyTags.delete(tag)
if (!keyTags.size) {
keyMap.delete(key)
}
}
tagMap.delete(tag)
}
}
return Promise.resolve()
}
purgeCache(cacheType: CacheType): Promise<void> {
this.tagToKeys.get(cacheType)!.clear()
this.keyToTags.get(cacheType)!.clear()
return Promise.resolve()
}
removeCacheItem(cacheType: CacheType, key: string | string[]): Promise<void> {
const tagMap = this.tagToKeys.get(cacheType)!
const keyMap = this.keyToTags.get(cacheType)!
const keysToRemove = Array.isArray(key) ? key : [key]
for (const key of keysToRemove) {
const keyTags = keyMap.get(key)
if (!keyTags) {
return Promise.resolve()
}
for (const tag of keyTags) {
const tagKeys = tagMap.get(tag)!
tagKeys.delete(key)
if (!tagKeys.size) {
tagMap.delete(tag)
}
}
keyMap.delete(key)
}
return Promise.resolve()
}
purgeEverything(): Promise<void> {
for (const cacheType of CACHE_TYPES) {
this.tagToKeys.get(cacheType)!.clear()
this.keyToTags.get(cacheType)!.clear()
}
return Promise.resolve()
}
addCacheTags(
cacheItemKey: string,
cacheType: CacheType,
cacheTags: string[],
): Promise<void> {
const tagMap = this.tagToKeys.get(cacheType)!
const keyMap = this.keyToTags.get(cacheType)!
let keyTags = keyMap.get(cacheItemKey)
if (!keyTags) {
keyTags = new Set()
keyMap.set(cacheItemKey, keyTags)
}
for (const tag of cacheTags) {
keyTags.add(tag)
let tagKeys = tagMap.get(tag)
if (!tagKeys) {
tagKeys = new Set()
tagMap.set(tag, tagKeys)
}
tagKeys.add(cacheItemKey)
}
return Promise.resolve()
}
}
typescript
import { defineMultiCacheOptions } from 'nuxt-multi-cache/server-options'
import { InMemoryCacheTagRegistry } from './registry'
export default defineMultiCacheOptions(() => {
return {
cacheTagRegistry: new InMemoryCacheTagRegistry(),
}
})