---
title: "Runtime Cache"
description: "Wrap Saturday's stock-check warehouse call with Next.js 16's 'use cache' directive so repeat lookups for the same SKU and size hit the Runtime Cache instead of invoking the function path that talks to the warehouse."
canonical_url: "https://vercel.com/academy/optimize-your-vercel-account/runtime-cache"
md_url: "https://vercel.com/academy/optimize-your-vercel-account/runtime-cache.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-07-22T02:29:36.982Z"
content_type: "lesson"
course: "optimize-your-vercel-account"
course_title: "Optimize Your Vercel Account"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Runtime Cache

# Runtime Cache

It's drop day. Ten thousand sneakerheads have your stock-check endpoint queued in browser tabs, fingers on the refresh button. The Foundry just went live in size 10 with 47 pairs available. Your function runs 10,000 times, calls the warehouse 10,000 times, and the warehouse says the same thing 10,000 times: yes, 47 of size 10. Then 46. Then 45.

You don't need to ask the warehouse 10,000 times for the same answer. You need to ask it once and remember the result for a few seconds. That's what the Runtime Cache does. Wrap the slow call in `'use cache'`, give the cached result a short revalidation window, and the next thousand requests for the same SKU and size answer themselves from a regional cache instead of invoking the warehouse path.

The function still runs. It just stops doing the expensive thing on every call.

\*\*Note: What is the Runtime Cache?\*\*

A regional cache for the results of server-side work. In Next.js 16 you
reach it with the `'use cache'` directive: the function's return value is
stored keyed by its arguments, so repeat calls read the stored answer
instead of doing the work again.

## Outcome

`fetchStockFromWarehouse` in `app/api/stock-check/route.ts` is wrapped with `'use cache'` so repeat requests for the same SKU and size are served from the Runtime Cache.

## Hands-on exercise 3.2

Two file changes. The cache directive on the function, and a single config flag to enable cache components.

**Requirements:**

1. Open `next.config.ts` and add `cacheComponents: true` at the top of the config object. The `'use cache'` directive requires this flag in Next.js 16.
2. Open `app/api/stock-check/route.ts`
3. Import `unstable_cacheTag` and `unstable_cacheLife` from `next/cache`, aliasing them to `cacheTag` and `cacheLife`
4. Add `'use cache'` as the first line inside `fetchStockFromWarehouse`
5. Tag the cached result with ``cacheTag(`stock-${sku}`)`` so we can invalidate by SKU later
6. Set the cache lifetime with `cacheLife({ revalidate: 60, expire: 300 })` (refresh after a minute, hard expire after five)
7. Commit, push, redeploy

**Implementation hints:**

- The `'use cache'` directive is function-scoped. Putting it at the top of `fetchStockFromWarehouse` caches that function's results keyed by its arguments. The cache is regional, each Vercel region maintains its own.
- `revalidate: 60` doesn't mean the cache is purged every 60 seconds. It means: on the first request after 60 seconds, serve the stale value and refresh in the background. The user sees an instant response.
- `expire: 300` is the hard ceiling. After 5 minutes the cached value is gone and the next request waits for a fresh warehouse call.
- The tag (`stock-${sku}`) is your invalidation handle. From anywhere in the app you can call `revalidateTag('stock-sat-foundry-onyx')` to dump the cached entry for that one SKU. That's how a CMS webhook would tell the site about new inventory.

## Try It

After the redeploy lands, send the same stock-check request twice in a row:

```bash
time curl -s -X POST https://YOUR_DOMAIN/api/stock-check \
  -H "Content-Type: application/json" \
  -d '{"sku":"sat-foundry-onyx","size":10}'

time curl -s -X POST https://YOUR_DOMAIN/api/stock-check \
  -H "Content-Type: application/json" \
  -d '{"sku":"sat-foundry-onyx","size":10}'
```

The first call should look like this:

```
{ "available": true, "count": 5 }
real    0m0.847s
```

The second call should look like this:

```
{ "available": true, "count": 5 }
real    0m0.082s
```

The first request fills the cache. The second hits the cache and skips the 800ms warehouse simulation. Same response body. Tenth of the time.

Now try a different size:

```bash
time curl -s -X POST https://YOUR_DOMAIN/api/stock-check \
  -H "Content-Type: application/json" \
  -d '{"sku":"sat-foundry-onyx","size":11}'
```

That's a fresh argument tuple, so it's a fresh cache key, so it's a slow first call:

```
{ "available": true, "count": 11 }
real    0m0.831s
```

The size-10 cache stays warm. The size-11 cache is now also warm. This is how caching by arguments works in practice.

## Done-When

- [ ] `next.config.ts` has `cacheComponents: true`
- [ ] `app/api/stock-check/route.ts` imports `unstable_cacheTag` and `unstable_cacheLife`
- [ ] `fetchStockFromWarehouse` begins with `'use cache'`
- [ ] Second curl request to the same SKU/size returns in under 200ms
- [ ] Observability > Functions shows fewer invocations of `/api/stock-check` running the slow path during repeat traffic

## Troubleshooting

**TypeScript complains that `'use cache'` isn't a valid directive.**

You're missing the `cacheComponents: true` flag in `next.config.ts`, or your Next.js version is below 16. Check `package.json` and confirm `next` is `^16.0.0`. If it's older, upgrade.

**The second request still takes 800ms.**

A few possibilities. The deployment might not have rebuilt yet, check the deployment timestamp. The cache might be partitioned by region, and your second request might have hit a different one, try several requests in a row from the same location. Or your tag and lifetime might not have applied because of a syntax error above them; check the build logs for warnings.

**The cache returns stale data for too long.**

The combination of `revalidate: 60` and `expire: 300` means stale data can be served for up to 5 minutes. If that's too long for inventory, lower `expire`. If you want truly fresh data on every request, you don't want caching for this route at all, and that's a fair call for some endpoints. The win here only exists because stock counts changing every 60 seconds is acceptable for a small indie brand.

## Solution

The finished `app/api/stock-check/route.ts` is on the [`complete` branch](https://github.com/vercel-labs/academy-optimize-vercel-account/blob/complete/app/api/stock-check/route.ts) if you want to check your version against it.

The updated function:

```ts title="app/api/stock-check/route.ts"
import { NextResponse } from "next/server";
import {
  unstable_cacheTag as cacheTag,
  unstable_cacheLife as cacheLife,
} from "next/cache";
import { getProductById } from "@/app/data/products";
import type {
  StockCheckRequest,
  StockCheckResponse,
} from "@/app/lib/types";

async function fetchStockFromWarehouse(
  sku: string,
  size: number
): Promise<StockCheckResponse> {
  "use cache";
  cacheTag(`stock-${sku}`);
  cacheLife({ revalidate: 60, expire: 300 });

  await new Promise((resolve) => setTimeout(resolve, 800));

  const product = getProductById(sku);
  if (!product || !product.sizes.includes(size)) {
    return { available: false, count: 0 };
  }

  const deterministicCount = ((sku.length * size * 7) % 12) + 1;
  return { available: true, count: deterministicCount };
}

export async function POST(request: Request) {
  const body = (await request.json()) as Partial<StockCheckRequest>;
  if (!body.sku || typeof body.size !== "number") {
    return NextResponse.json(
      { error: "sku and size are required" },
      { status: 400 }
    );
  }
  const result = await fetchStockFromWarehouse(body.sku, body.size);
  return NextResponse.json(result);
}
```

And the config flag:

```ts title="next.config.ts"
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;
```

The cache shields the warehouse, the firewall shields the function. Together they handle Saturday's drop-day traffic shape, many requests, few unique answers.

Up next: the drops page itself is on a timer. Let's fix that.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
