[▲ Vercel Community](/) · [Categories](/categories) · [Latest](/latest) · [Top](/top) · [Live](/live)

[Feedback](/c/feedback/8)

# Allow adding cache tags that depend on the response of `fetch` or `unstable_cache`

135 views · 1 like · 3 posts


Romeovs (@romeovs) · 2024-10-17

Cross-posting this from [GitHub discussion](https://github.com/vercel/next.js/discussions/65521), in the hopes this gets more traction here.

### Usecase

In some cases it might be useful to provide cache tags to a cache entry, _after_ the result has been received.

For instance, I might fetch a product from the api:

```js
await unstable_cache(
  () => api.products.get({ id }),
  ["product", id], 
  {
    tags: [`product:${id}`],
  },
)()
// { type: "product", id: 42, ... }
```
I pass the `product:$42` tag so I can invalidate the cached item when the product with that `id`  changes.
It is possible, because we know the `id` we're trying to fetch ahead of time and so we can add the tag when calling `unstable_cache`.

Additionally, I might want to include related products in the response:
```js
await unstable_cache(
  () => api.products.get({ id }, { related: true }),
  ["product", id],
  {
    tags: [`product:${id}`],
  },
)
// { type: "product", id: 42, related: [{ type: "product", id: 85 }, ...], ... }
```

Here it is incorrect to only add `product:42` as a tag, since the result also contains data from other products.
We should also pass `product:85` and other product tags from the response so we can correctly invalidate the response when related products change.

The way `unstable_cache` works now, it is not possible to do this atomically.

### Proposal

It would be useful if we could calculate the tags from the response in `unstable_cache` so the tags can be set atomically:

In next something similar could look like:

```js
await unstable_cache(
  () => api.products.get({ id }, { related: true }),
  ["product", id],
  {
    tags(product) {
      // return the related product's tags too
      return [`product:${id}`, ...product.related.map(p => `product:${p.id}`)]
    }
  },
)
// { type: "product", id: 42, related: [{ type: "product", id: 85 }, ...], ... }
```

This will allow sites to cache responses _way_ more agressively, since they can tag the cache entries with info about their _content_.
Being able to this is a major blocker for me to do more fine-grained caching.



### Background

One example where a similar pattern is used is [RTK Query](https://redux-toolkit.js.org/rtk-query/usage/automated-refetching), where [`providesTags`](https://redux-toolkit.js.org/rtk-query/api/createApi#providestags) is allowed to be a function that takes the response as an argument and returns a list of tags.

### Workaround

It _does_ seem to be possible to add cache tags to an existing cache key (without invoking the cached function again), but that was only clear to me when I read the `unstable_cache` code and I think it's an implementation detail, not an API guarantee.


Additionally, this is not atomic, so it might happen that a cache tag gets revalidated using `revalidateTag` between the first and second call to `unstable_cache` in the above function will cause the cache entry to not be cleared properly.

```ts
function async workaround_cache(fn, key, options) {
  const res = await unstable_cache(fn, key, {
    tags: typeof options.tags === "function" ? [] : options.tags,
  })
  const tags = typeof options.tags === "function" ? await options.tags(res) : options.tags

  // A second call does not call fn again, but because the key is the same, but it
  // will add the tags to the existing cache entry.
  // (!) DANGER: this is relying on an implementation detail
  // (!) DANGER: this introduces a race condition
  return await unstable_cache(fn, key, {
    tags,
  })
}
```

It would be good to add this to the `unstable_cache` API. I'm happy to help and set up a PR if this seems like a good idea.


Romeovs (@romeovs) · 2024-10-22 · ♥ 1

It seems like the new `"use cache"` system would allow this to work, as long as we can call `cacheTag` *after* `await`!

```
function cached() {
  const res = await fn()
  const tags = options.tags(res)
  cacheTags(tags)
  return res
}


Pauline P. Narvas (@pawlean) · 2024-11-05

Let us know if you have any more questions, @romeovs :slight_smile: Happy to pass onto the Next.js team!