Next.js / Intermediate: authority, freshness and route experience / Lesson 9
Traditional cache layers and freshness policy
Reason about fetch storage, prerendered output and client navigation without mixing them with Cache Components.
On this page
Lesson goals
- Separate request memoization, persistent data storage and reused route output.
- Choose explicit audience, key, lifetime and invalidation contracts.
Name the cache and selected mode before diagnosing staleness
This lesson assumes cacheComponents:false. Render-pass memoization, server fetch caching, prerendered route output and client navigation state are different layers. A fetch not opted into persistent caching can still be executed during prerendering and have its result represented in reused route output. Conversely, a dynamic route can call an explicitly cached data helper. Avoid the slogans that all server fetches are cached forever or that uncached defaults guarantee every visible value is read anew. Identify exactly which representation is reused, for which audience and under which configuration before changing options.
Opt in only a reviewed public representation
The fragment opts a synthetic public catalog request into force-cache with a sixty-second revalidation setting and a public tag. The interval is a lab freshness choice, not a tuning recommendation. It does not imply that account or permission data should use the same policy. The Next extension’s revalidate value uses seconds, and options must be compatible rather than combining no-store with a positive revalidation lifetime. The endpoint is .invalid and is not contacted. Verify exact cache behavior against the selected version and deployment rather than inferring persistent storage from this unexecuted fragment.
async function publicCatalog() {
const response=await fetch("https://catalog.example.invalid/public-courses",{
cache:"force-cache",next:{revalidate:60,tags:["public-courses"]}
});
if (!response.ok) throw new Error("CATALOG_UNAVAILABLE");
return validatePublicCatalog(await response.json());
}
The request is a nonexecuting example. Lifetime, source validation, audience and revalidation all require actual reviewed integration.
A key separates values but does not grant access
A shared data representation may vary by tenant, locale, filter, source revision or schema version. Include the required dimensions in the domain contract and review every producer and consumer. Do not place tokens or private personal values in keys exposed to logs. The framework has its own cache-key algorithms; a teaching key model is not a reproduction of them. Personalized data should not be put into a shared cache merely because a function is server-side. Even a correctly scoped key needs current authorization at the boundary where the value is returned.
Test domain identity dimensions locally
The local key uses JSON encoding for a deterministic component array. It distinguishes tenant and locale while keeping a schema version explicit. This is a domain naming convention, not the internal Next cache identity. The assertions open no cache and establish no expiry or isolation behavior in a deployed system. Real stores and tags may impose length and character rules, so choose a compatible representation deliberately. A later code path that omits a dimension can still mix data even when this helper is correct, which is why shared construction and alternate-path tests matter.
function key(tenant,id,locale,version) {return JSON.stringify(["card",tenant,id,locale,version]);}
console.assert(key("lab-a","c1","en",1) !== key("lab-b","c1","en",1));
console.assert(key("lab-a","c1","en",1) !== key("lab-a","c1","ar",1));
console.assert(key("lab-a","c1","en",1) !== key("lab-a","c1","en",2));
Each changed declared dimension produces a different key. No framework cache key, storage or invalidation is tested.
Review invalidation, late fills and infrastructure together
Expiry bounds lifetime under a policy but does not provide transaction ordering. An old in-flight source read can repopulate stale data after a write and invalidation. Distinguish genuine absence from source failure before negative caching. Revalidation APIs affect specific layers and contexts; a browser refresh is not a universal purge. Self-hosted or multi-instance deployments need a reviewed cache-sharing and rollout contract. This course checks the key function only, not render memoization, persistent storage, prerender output or timed revalidation. Verify those with an authorized build/request integration and safe fixtures rather than assuming development observations represent production.
Common errors and fixes
An uncached fetch is assumed to prevent prerendered output reuse
- Why it happens
- Fetch-cache policy and route-output production were conflated.
- How to fix it
- Inspect data options and route rendering separately.
- Prevent it next time
- Retain build-time and request-time fixtures.
Private cards share one public cache entry
- Why it happens
- Audience dimensions were missing or the representation was incorrectly declared public.
- How to fix it
- Correct representation scope and enforce current access policy.
- Prevent it next time
- Test different principals against the same visible path.
A source outage is cached as an empty catalog
- Why it happens
- Failure was treated as legitimate source absence.
- How to fix it
- Keep safe error and absence contracts distinct.
- Prevent it next time
- Test cache miss, empty source and failed source independently.
Check your understanding
1. Does the key model reproduce Next’s internal cache algorithm?
Show worked solution
No. It tests a local domain convention for values that vary by tenant, locale and schema. Next uses its own versioned keys and storage rules. Use the model to make audience requirements explicit, then verify the real framework integration. A correct local key cannot prove all callers use it or that a deployed cache enforces isolation.
2. Why can a short lifetime still permit a stale result after invalidation?
Show worked solution
A previously started source read may finish after the invalidation and write an old value back. The lifetime limits how long it may survive under the policy but does not remove the race. Review source revision or generation checks where required, and do not describe a TTL as a guarantee of strong consistency.
Key takeaways
- Traditional cache layers must be named and tested separately.
- Audience, lifetime and ordering are different guarantees.