tRPC Cache-Control Policy
tRPC Cache-Control Policy
As of v0.1.178, all tRPC API responses include explicit Cache-Control headers. The behaviour is determined by a dedicated cache policy module and is applied automatically at the route handler level — no changes to individual procedures are required.
Overview
The policy classifies every tRPC request into one of three tiers and sets the appropriate Cache-Control header before the response is sent.
POST / mutation → no-store
GET (sensitive) → no-store
GET (static) → public, s-maxage=60, stale-while-revalidate=300
GET (default) → private, no-cache
Tiers in Detail
Tier 1 — Never Cached (Mutations)
Applies to: All POST requests (i.e. any tRPC mutation).
Cache-Control: no-store
Mutations change server state and must never be served from a cache. This tier is unconditional.
Tier 2 — Never Cached (Sensitive GETs)
Applies to: GET requests where the procedure involves personally identifiable information (PII), secrets, or per-user state.
Cache-Control: no-store
Examples of procedures that fall into this tier include anything touching user profiles, authentication tokens, deposit case data, or tenant-specific records.
Tier 3 — Public CDN Cache (Static GETs)
Applies to: GET requests for fully static, globally shared reference data.
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
| Parameter | Value | Meaning |
|---|---|---|
s-maxage | 60 s | CDN edge caches the response for 60 seconds. |
stale-while-revalidate | 300 s | CDN may serve a stale response for up to 5 minutes while revalidating in the background. |
Procedures currently in this tier:
- Plans
- Wear-and-tear categories
- Compliance help content
- Inventory templates
- Agent roles
Because these procedures return identical data for all organisations, it is safe for a CDN edge node to cache and serve them to multiple callers.
Tier 4 — Private Browser Cache (Default)
Applies to: All other GET requests (org-scoped data that is not explicitly sensitive).
Cache-Control: private, no-cache
The private directive ensures the response is stored only in the requesting browser's cache and never at a shared CDN edge. no-cache means the browser must revalidate with the server before using a stored response.
Batch Request Handling
tRPC supports batching multiple procedure calls into a single HTTP request. The policy uses taint propagation to ensure safety: if any procedure in a batch would be classified as no-store, the entire batch response receives no-store.
This means a batch containing one sensitive procedure and several static procedures will always be no-store — the most restrictive policy wins.
Data Isolation Guarantee
Org-scoped data is always private and never reaches the CDN edge. Only globally identical static reference data can be publicly cached. Tenant data isolation is fully preserved.
Implementation
The policy is implemented in two files:
| File | Purpose |
|---|---|
src/lib/trpc/cache-policy.ts | Classifies requests and returns the correct Cache-Control header string. |
src/app/api/trpc/[trpc]/route.ts | tRPC route handler — calls the policy module and attaches the header to every response. |
The module is fully unit-tested (tests/lib/trpc/cache-policy.test.ts, 19 tests) covering URL parsing, all policy branches, and batch taint propagation.
Adding a Procedure to the Static Tier
To mark a new procedure as publicly CDN-cacheable, it must meet all of the following criteria:
- Returns identical data regardless of which organisation or user calls it.
- Contains no PII, secrets, or user-specific state.
- Is a query (not a mutation).
If all criteria are met, add the procedure name to the static allowlist in src/lib/trpc/cache-policy.ts. Ensure a unit test is added to tests/lib/trpc/cache-policy.test.ts to cover the new entry.