Your portal works in dev, then a price revision takes forty minutes to appear, or a sales manager opens a dashboard and sees numbers that belong to someone else. The App Router moved caching into four separate layers, each with its own invalidation rule, and most teams ship to production without setting any of them deliberately. The teams that feel this hardest are mid-market Indian B2B outfits: three to six developers, one staging environment, and a catalogue that finance updates every morning.
How many cache layers does the App Router actually have?
There are four, and each one fails in a different way. Request memoization dedupes identical fetches inside a single render pass and is the least dangerous of the set. The Data Cache persists fetch and database results across requests and across deploys. The Full Route Cache stores the pre-rendered HTML and RSC payload produced at build time. The Router Cache lives in the browser and holds prefetched segments as users navigate. Layer on top of that revalidatePath and revalidateTag as the invalidation API, and you have five things that can each hand back yesterday's answer. The single mental shift worth internalising: in the App Router a route is static unless something forces it dynamic — reading cookies(), reading headers(), touching searchParams, or an explicit opt-out. Silence means caching.
The worked example: a dealer portal serving stale prices
A representative case from our project work: a mid-market industrial distributor, roughly 180 staff with head office in Pune and 14 branches, replacing a jQuery-era dealer portal with about 40 routes on Next.js 14. Their ERP pushed revised price lists every 15 minutes to a REST endpoint. Two things broke. First, the catalogue route had no revalidation configured, so it was baked at deploy time and served the price list from build day — dealers quoted old rates for two weeks before accounts flagged it. Second, the account orders page used a plain fetch with an Authorization header and no cache directive; on Next.js 14 that defaults to force-cache, so in staging two test accounts briefly rendered the same order list. We caught that one before go-live, but only because someone logged in twice. Note the version trap: Next.js 15 flipped the fetch default to no-store, so a codebase upgraded from 14 can silently change caching behaviour without a line of application code changing.
Which cache layer is serving the wrong data?
Match the symptom to the layer before you change any code. Most of these bugs are diagnosable in minutes once you know where to look.
- Stale for everyone, and a redeploy fixes it: the Full Route Cache — the route is static and nothing is invalidating it.
- Stale for one dataset, correct after a hard reload: the Data Cache — your revalidateTag or revalidatePath is not firing from the write path.
- Old content when navigating back and forth, fresh in a new tab: the Router Cache — set staleTimes in next.config, or call router.refresh() after mutations.
- One user seeing another user's data: the route was pre-rendered without an auth signal; read the session with cookies() at the top of the layout or page.
- Identical fetches running twice in one render: that is request memoization working correctly; leave it alone.
We usually start by drawing the data flow and tagging each fetch with the layer that owns it — our architecture review process exists largely for this step.
The rules we now apply on every App Router build
Authenticated routes read the session with cookies() in the layout, which opts the whole segment into dynamic rendering without extra config. Anything with an owner gets an explicit cache: 'no-store' rather than relying on defaults that changed between versions. Public catalogue data gets tagged instead — fetch with next: { tags: ['price-list'], revalidate: 900 }, then fire revalidateTag('price-list') from the ERP push webhook, so invalidation is driven by the actual data change and not a TTL guess. After any mutation, revalidatePath on the specific path, never on '/'. And if you are self-hosting rather than on Vercel, remember the default file-system cache does not survive more than one instance: either add a shared cache handler backed by Redis or pin cache reads to a single node. We get into that in running Next.js on your own servers.
What the numbers looked like after
Three changes — tagged revalidation on the catalogue, an explicit no-store on account routes, and a Redis cache handler — took one engineer about three days spread over two weeks. Catalogue p95 TTFB dropped from roughly 780ms to 210ms, because cached HTML still gets served for anonymous dealer traffic while the tag flushes it four times an hour. Calls to the ERP's price endpoint fell from about 41,000 a day to under 6,000, which their IT team noticed before we did. Build output moved from 42 routes wrongly marked static to 11 static and 31 dynamic. The stack stayed on the same two-vCPU VPS — no new infrastructure spend. The "wrong price" complaints, roughly 30 a month before, went to zero in the following quarter.
Where to start
Pick your three most-viewed routes and write down, for each one, which cache layer is allowed to hold its data and what event is permitted to flush it. That single page of notes resolves most of these bugs before you touch code. If you want a second pair of eyes on it, Maysan Labs builds and audits Next.js platforms for B2B teams from Gurgaon and Cardiff.
Buying software or a website?
Twelve questions to ask any vendor before you sign: price, your data, fit for an Indian business and support.
Get the free checklistWritten by Maysan Engineering Team at Maysan Labs, a software studio in Gurgaon, India. Follow us on LinkedIn