Skip to content
Chapter 72Lesson 3

Quiz - Cache decisions as architecture

Quiz progress

0 / 0

You’re deciding whether to add 'use cache' to a route. One candidate is the per-user invoices list with ?status=overdue URL filters; another is the app chrome (nav, plan badge) that every user loads on every page. Which is the better caching candidate, and why?

The app chrome — it’s a shared, stable value read by many users many times between writes, which is exactly the high read-to-write ratio that earns a cache.

The filtered invoices list — it does the most database work per request, so caching it saves the most server time.

Neither — authenticated SaaS surfaces should always stay dynamic regardless of read frequency.

An invoice detail read and an invoice list read both live in the same org. At what tag granularity should each one tag itself?

Detail tags the record (invoiceTags.record(orgId, id)); list tags the org-scoped list (invoiceTags.list(orgId)) — each tags at the grain the read actually needs.

Both tag every record they reference, so a single edited row invalidates the most precisely.

Both tag only orgTags.all(orgId), so any org change refreshes everything at once.

This cached function compiles, type-checks, and works in dev with one logged-in user. What goes wrong in production?

export async function listInvoices() {
'use cache';
const { orgId } = await auth();
cacheLife('max');
cacheTag(invoiceTags.list(orgId));
return tenantDb(orgId).select().from(invoices);
}

The session read inside the cached body bakes the first org’s data into one shared entry with no org in the key — every other org then reads org A’s invoices. A cross-tenant data leak.

auth() throws because request data can never be read anywhere inside a cached function’s call stack.

Nothing — cacheTag(invoiceTags.list(orgId)) scopes the entry per org, so each org gets its own cache key.

You’re picking a cacheLife profile for a cached read. What are the three numbers behind a profile actually describing?

How long the user tolerates slightly-old data — a product question about freshness, not a server-performance knob.

How much server CPU the recompute is allowed to consume before the cache gives up.

How many concurrent readers can share one cached entry before it’s recomputed.

The single most common misconception in this topic: what does router.refresh() actually do to a cached read whose tag is still valid?

Nothing — it re-runs the route’s server render, but a cached read with a valid tag serves the same value again. Only invalidating the tag expires the read.

It expires every cached read on the current route, forcing all of them to refetch.

It expires only the cached reads whose tags match the current URL path.

The same mutation — a user’s display-name change — can resolve to a different invalidation call depending on context. A Server Action where the user just edited their own name versus a webhook from an external identity provider pushing the change. Which calls, in that order?

updateTag for the action (read-your-writes, the user is watching the redirect), revalidateTag for the webhook (eventual, nobody’s waiting).

updateTag for both — the user’s data changed in both cases, so read-your-writes applies either way.

revalidateTag for both — display names are reference data, so stale-while-revalidate is always correct.

Inside a Server Action that edits an invoice and its line items in a db.transaction, then redirects to the detail page, where does the updateTag invalidation belong?

After the transaction commits, before the redirect.

Inside the transaction, right after the row updates, so the cache and the database change atomically.

After the redirect — the redirect’s fresh render is what triggers the cache to refresh.

A Trigger.dev nightly job rebuilds an org’s analytics summary in a separate process from the web server, then needs to invalidate the dashboard’s cached read. Which call, and does cross-process work?

revalidateTag(orgTags.all(orgId), 'max') — imported from next/cache and called directly; the framework routes it through the deployment’s shared cache backend.

updateTag(orgTags.all(orgId)) — the freshest possible read is best for the morning’s first dashboard view.

Neither works from a background process; the job must POST to a Next.js route handler that does the invalidation.

Quiz complete

Score by topic