---
title: Optimize Runtime Cache on Vercel
description: Diagnose rising Runtime Cache usage, trace reads to tags and code paths, and reduce avoidable reads while checking write cost and origin traffic.
url: "https://vercel.com/kb/guide/optimize-runtime-cache-reads-on-vercel"
published: 2026-10-28
last_updated: 2026-10-30
authors: Joel Fickson, Kelly Davis
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Runtime Cache usage can rise for three different reasons: more function invocations reached the cache, each invocation performed more reads, or each read returned more data. Compare function invocations, reads per invocation, and entry size to separate those causes. Then find the routes and cache keys behind the usage, and remove reads and writes that do no work.

## Understand Runtime Cache usage

The runtime cache is a regional key-value cache shared by Vercel Functions, Routing Middleware, and builds. Your code reads from it in two ways:

- Calls to `cache.get()` from the `getCache` helper in `@vercel/functions`, in any framework.
  
- The `use cache: remote` directive in Next.js 16, which requires the `cacheComponents` flag and stores the output of a function or component in the runtime cache, reading it back on later requests.
  

Reads and writes are billed in 8 KB units rounded up for each operation. Reading a 1 KB entry uses one read unit, while reading a 100 KB entry uses 13.

The [regional price](https://vercel.com/docs/pricing/regional-pricing) per million units ranges from $0.40 to $0.64 for reads and $4.00 to $6.40 for writes. Because a write unit costs ten times as much as a read unit, compare cost when both metrics increase.

Useful cache hits can reduce compute and origin requests even when the read count is high. Focus on repeated reads of the same key within a request, unused data, and keys that rarely hit.

The Usage dashboard reports [ISR](https://vercel.com/docs/incremental-static-regeneration/limits-and-pricing) separately from Runtime Cache. Confirm which metric increased before investigating the code.

Next.js App Router apps can also use the [Data Cache](https://vercel.com/docs/caching/runtime-cache/data-cache) for `fetch` calls with `force-cache` or a positive `next.revalidate`, and for functions wrapped in `unstable_cache`. Its activity appears in the Runtime Cache area of Observability.

Data Cache is merging into Runtime Cache. If your app uses it, follow [Optimize Data Cache usage before you migrate](#optimize-data-cache-usage-before-you-migrate) in step four.

## Before you begin

Compare the same project and environment across both time windows:

- Select one team and project. On Hobby, projects in a team share Runtime Cache storage. Data Cache storage is shared on Hobby and Pro, so writes from another project can affect eviction.
  
- Use one environment. Production and preview have separate caches.
  
- Choose equal-length windows before and after the increase.
  

Choose both comparison windows within your available Observability history: 12 hours on Hobby, one day on Pro, three days on Enterprise, or 30 days with Observability Plus.

## 1\. Confirm the usage source

1. Open **Usage** and select the period containing the increase.
   
2. Compare the projected cost of **Runtime Cache Reads** and **Runtime Cache Writes**, then group the metrics by project and region to locate the increase.
   
3. Record whether reads rose on their own or alongside writes. Use that pattern in step 3 to narrow the likely cause.
   

## 2\. Find the tags and code paths driving reads

Open your project, then go to **Observability** → **Runtime Cache**. Until your team migrates, the page opens on Data Cache, so switch to the Runtime Cache view before reading the charts.

The page shows reads, writes, hit rate, and on-demand revalidations, followed by a cache-tag table. Observability Plus adds **Reads**, **Writes**, **Revalidate**, and **Unique Entries** to the **Tag** and **Hit Rate** columns.

1. With Observability Plus, sort the tag table by **Reads** to identify the tags contributing most usage.
   
2. Search your codebase for those tags in `cacheTag(...)` calls and the `tags` option of `cache.set(...)`. Without tag-level read counts, inspect calls to these APIs to identify possible sources.
   

Divide cache reads by function invocations from **Observability** → **Vercel Functions** over the same window. Compare the result with the number of entries a request needs to spot repeated or looped reads.

Routing Middleware and builds can add cache reads without adding function invocations. If either reads the cache, the project-wide ratio includes those reads and cannot be treated as an exact per-route count.

The Entries table shows each entry's median size, measured on reads served from the cache, in its Entry Size column. The tag table has no size column, so look up a hot tag's entries there. For an entry with no cached reads yet, estimate the serialized size of the value your code stores, and record byte counts rather than cached values. Use the billing unit definition to translate the size into an estimated read-unit count.

## 3\. Interpret the pattern

| What you see                                                           | Likely cause                                                 | Where to look next                                                                       |
| ---------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| Reads and invocations rose by the same factor                          | More function invocations reached the cache                  | Check reads and units per invocation, then compare traffic, CDN hit rate, and bot share. |
| Reads per invocation rose while traffic stayed flat                    | Recent code changes added reads per request                  | Compare deployments around the increase.                                                 |
| Read units rose while invocations and reads per invocation stayed flat | Cached entries grew                                          | Measure values under the affected tags and look for fields callers do not use.           |
| Hit rate fell                                                          | More distinct keys, shorter time-to-live values, or eviction | Check key construction, expiration settings, and projects sharing the same storage.      |
| Reads and writes rose together                                         | Short expiration times or keys that vary per request         | Inspect TTL, `cacheLife`, and how keys are constructed.                                  |
| Reads appeared after adding `use cache: remote`                        | Caching moved into Runtime Cache                             | Check whether the cached work justifies shared storage and network reads.                |
| Misses and writes rose after a deployment                              | Deployment-dependent keys produced a cold cache              | Check cache-key persistence across deployments.                                          |

If a helper fetches and caches a value after a miss, concurrent requests can repeat that work before the entry is populated. `use cache: remote` can serve stale output during background revalidation until its expiration requires a fresh result.

For `use cache: remote`, keys include the deployment ID when configured, or the build ID. Explicit `getCache` keys, cached `fetch` calls, and `unstable_cache` can remain readable across deployments, subject to expiration and eviction. Check [how deployments affect cached data](https://vercel.com/docs/caching/runtime-cache#how-a-new-deployment-affects-cached-data) before changing APIs.

## 4\. Common causes and fixes

### The same key is read several times in one request

When several Server Components call a helper that reads `cache.get()`, they can read the same key repeatedly during one server render.

Read the value once and pass it to callers, or export one helper wrapped in [React's](https://react.dev/reference/react/cache) [`cache()`](https://react.dev/reference/react/cache) so those callers share its result.

The helper below assumes your application provides `fetchSettingsFromOrigin()`. Keep it in a server-side module.

```typescript
import { cache } from 'react';
import { getCache } from '@vercel/functions';

// Import your application's fetchSettingsFromOrigin helper in this module.
export const getSiteSettings = cache(async () => {
  const runtimeCache = getCache();
  const cached = await runtimeCache.get('site-settings');
  if (cached !== null) return cached;

  const settings = await fetchSettingsFromOrigin();
  await runtimeCache.set('site-settings', settings, {
    ttl: 3600,
    tags: ['settings'],
  });
  return settings;
});
```

Five calls to this exported helper in the same Server Component render scope perform one runtime-cache lookup. Separate `use cache` boundaries have their own React caches. In Route Handlers, middleware, or other code outside a React render, read once and pass the result or share a promise within that request.

### Reads inside loops

Per-item keys read in a loop turn one request into N reads. Both excerpts return the same 24 featured products. They assume the entries are populated and your application provides `getProductIds()`.

On a miss, `cache.get()` returns `null`. Your handler must fetch the missing data and populate the cache before returning a complete response; the excerpts omit that logic to show the lookup difference.

```typescript
import { getCache } from '@vercel/functions';

// Excerpt from app/api/products/route.ts; miss handling is omitted.
// Import your application's getProductIds helper in this module.
export async function GET() {
  const ids = await getProductIds(); // The 24 featured product IDs
  const cache = getCache();
  const products = await Promise.all(
    ids.map((id) => cache.get(`product:${id}`)),
  );
  return Response.json(products);
}
```

Group values that callers read together into one entry, within the 2 MB item limit. Under the 8 KB rounding rule, twenty-four separate 2 KB entries use 24 read units. If the complete serialized group is 48 KB, one read uses six units.

```typescript
import { getCache } from '@vercel/functions';

// Excerpt from app/api/products/route.ts; miss handling is omitted.
export async function GET() {
  const cache = getCache();
  const products = await cache.get('products:featured');
  return Response.json(products);
}
```

Keep separate keys when a route uses only a small part of the data or when items are invalidated independently. Retrieving a 500 KB entry to use 2 KB still reads the full value and uses 63 read units.

Invalidation alone does not write replacement data. When the grouped value is refreshed, the write includes the whole entry. Rewriting 500 KB uses 63 write units even if only one item changed.

### Keys that are too granular to hit

Keys that vary on every request reduce reuse and can cause repeated origin fetches and cache writes. For shared CMS content, `getCMSContent(language)` lets readers with the same language preference reuse one entry.

Keep private profile data separate and preserve the key values that distinguish users or tenants. Only filter shared data in memory when every caller is allowed to access it. Retrieve user-specific state from its authorized database or session store.

With Cache Components enabled, `use cache: private` can reuse output in the client router's browser memory. In production it does not retain a server cache across requests. Use it when request-specific data must stay out of persistent server caches, or when refactoring runtime reads into arguments is impractical.

### Caching tiny, static, or cheap data

Keep build-time values in the bundle or environment variables. Use [Global Config](https://vercel.com/docs/global-config) for frequent reads of configuration that changes without a deployment.

Remote lookups add network latency. An operation that already finishes in about 50 ms may gain little from remote caching, so compare the lookup with the work it replaces. Recompute the value when that costs less than fetching it.

See [when to avoid remote caching](https://nextjs.org/docs/app/api-reference/directives/use-cache-remote#when-to-avoid-remote-caching).

### Remote caching that costs more than it saves

Plain `use cache` stores results in memory with the default handler. You can configure a different handler through `cacheHandlers`. On Vercel, `use cache: remote` uses Runtime Cache, which adds a network lookup when it reads remote storage.

Choose the directive based on where the output needs to be reused:

- Output included in the static shell usually works with plain `use cache`. With the default handler, its cache storage stays in memory.
  
- Runtime data that needs reuse across function instances can benefit from `use cache: remote`. Plain `use cache` can reuse an entry while its instance remains alive, but other instances cannot read that entry and shutdown removes it. Switching to memory can therefore increase origin traffic.
  

Keep remote caching where sharing across instances saves enough origin work to justify the lookups. Compare origin traffic after changing the directive.

### Functions that run when the CDN could answer

Runtime Cache reads happen while your code runs in Vercel Functions, Routing Middleware, and builds. If the full response is cacheable, serve it from the CDN with [Cache-Control headers or ISR](https://vercel.com/docs/caching/cdn-cache).

Routing Middleware runs before the CDN cache, so caching the response does not remove reads made by middleware. Cache misses and ISR regeneration can still execute the function.

### Optimize Data Cache usage before you migrate

After your team migrates, Data Cache reads and writes are billed at [Runtime Cache rates](https://vercel.com/docs/pricing/regional-pricing). Use your current Data Cache usage to estimate that cost and prioritize optimizations before migrating

The same checks for repeated reads, key reuse, and cache lifetime also apply to Data Cache. In a Next.js app, inspect these patterns:

| Pattern                                           | In Data Cache                                                                  | Fix                                                                                                                                                                                         |
| ------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The same key is read several times in one request | An `unstable_cache` helper called by several Server Components in one render   | Export one helper wrapped in React's `cache()`. Identical `GET` fetches are already memoized during a server render unless you opt out with a signal; this does not apply in Route Handlers |
| Reads inside loops                                | `fetch` or `unstable_cache` called once per item                               | Fetch data that callers use together in one call and cache it as one entry, within the 2 MB item limit                                                                                      |
| Keys that are too granular to hit                 | Cached `fetch` URLs or `unstable_cache` arguments that change on every request | Cache shared content by a reusable value such as locale. Preserve user and tenant boundaries for private data                                                                               |
| Short revalidation                                | Low `next.revalidate` or `revalidate` values on data that rarely changes       | Raise the interval, or use [on-demand revalidation](https://vercel.com/kb/guide/how-to-move-to-on-demand-revalidation) when the data changes                                                |
| Functions that run when the CDN could answer      | Dynamic routes whose output is the same for every visitor                      | Prerender the route or use ISR so fresh cached responses avoid route execution. ISR regeneration still runs route code and can read the cache                                               |

#### Watch the lowest revalidate value

With Cache Components disabled, the lowest `revalidate` value across a statically rendered route's layouts, page, and cached fetches determines its ISR interval. Setting `next: { revalidate: 60 }` on a fetch in a shared layout can lower that interval to 60 seconds for each statically rendered route that uses it.

Regeneration starts when a request arrives after the interval has elapsed. It runs the route code, but each cached fetch retains its own revalidation lifetime, so regeneration does not rewrite every entry. Quiet routes do not regenerate once per minute merely because their interval is 60 seconds.

#### Check what your team shares

On Hobby and Pro, projects in a team share Data Cache storage. Heavy writes from one project can evict another project's entries and lower its hit rate, so check the team's busiest projects when investigating eviction. Enterprise projects have separate caches. See [storage scope by plan](https://vercel.com/docs/caching/runtime-cache/data-cache#storage-scope-by-plan).

#### Next.js 16 and Cache Components

Next.js 16 still supports Data Cache through explicitly cached `fetch` calls and `unstable_cache`. With `cacheComponents: true`, choose a `use cache` directive according to the storage your workload needs.

Plain `use cache` uses memory by default; `use cache: remote` uses Runtime Cache on Vercel when sharing across function instances justifies the reads. Review the [remote caching tradeoffs](#remote-caching-that-costs-more-than-it-saves) before changing existing code.

## 5\. Investigate with an agent (optional)

An agent with codebase access and the Vercel CLI can do the attribution and code inspection for you. Provide it:

- The project name and the timeframe of the increase.
  
- The numbers you collected in steps 1 and 2, including top tags, hit rate, reads, writes, and invocations.
  
- Read-only access. The agent must not edit files, change Vercel settings, purge caches, or deploy unless you approve a specific change.
  

Replace the project, team, environment, and date placeholders, then paste your dashboard numbers after the prompt.

Apply one proposed change at a time, then use step 6 to check the result.

## 6\. Verify the change

After deploying a fix, compare equal-length windows with similar traffic. Use the same project, region, and environment as the original investigation, and distinguish initial cache warm-up from sustained usage.

- Cache lookups per invocation should fall when the fix removes lookups from each request. Smaller entries can reduce billed units even if lookup counts stay flat. If the fix reduces function invocations through CDN caching, total reads can fall without changing the ratio.
  
- Compare total **Runtime Cache Reads** and projected read cost for the affected project and region.
  
- Compare the tag-table hit rate in both windows. It counts stale reads as hits, while the headline chart counts only fresh hits. Check origin traffic alongside it because serving stale data can still trigger background revalidation.
  
- Compare write cost with the earlier window. Grouping entries can reduce reads while making each refresh more expensive.
  
- Check function duration and origin traffic for added work. If you removed caching for a cheap operation, assess the total cost and latency tradeoff.
  

TTL and tag changes do not automatically update entries retained from earlier deployments. If a fix depends on new behavior for existing entries, plan targeted invalidation or a new key for the affected data. Include the resulting cache warm-up in your verification.

## Related resources

- [Runtime Cache](https://vercel.com/docs/caching/runtime-cache)
  
- [API reference for @vercel/functions](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package)
  
- [Next.js reference for use cache: remote](https://nextjs.org/docs/app/api-reference/directives/use-cache-remote)
  
- [Regional pricing](https://vercel.com/docs/pricing/regional-pricing)
  
- [Manage and optimize usage](https://vercel.com/docs/pricing/manage-and-optimize-usage)

## Related Documentation

- [Vercel Functions](/docs/functions)
- [Observability](/docs/observability)
- [Vercel CDN](/docs/cdn)

## More Vercel Functions guides

- [Deploy Rust on Vercel with Docker](/kb/guide/deploy-rust-on-vercel-with-docker): Build a Rust application with Axum and Docker, then deploy it to Vercel Functions. Learn how to configure environment variables, managed storage, and preview deployments for your project.
- [Vercel vs Railway](/kb/guide/vercel-vs-railway): A detailed guide to Vercel vs Railway: serverless vs always-on containers, container images from a Dockerfile, framework-defined infrastructure, AI infrastructure, global CDN, security, and when to choose each platform for your project.
- [Vercel vs Render](/kb/guide/vercel-vs-render): A detailed guide to Vercel vs Render: compute models, AI infrastructure, Docker and container image support, background workers, and when to choose each platform for your project.