I spent an afternoon debugging a component that kept re-fetching on every single request.
It had 'use cache' right there in the code. I was confid...
For further actions, you may consider blocking this person and/or reporting abuse
the wrapper placement bug bit us mid migration —
use cacheon the outer function, not inside the data function. spent an afternoon in Next.js DevTools wondering why server components were still showing stale data. the directive was right there, compiled fine, ran in prod.the PPR dynamic hole from
cacheLife('seconds')is the one i'd add to every migration checklist though. silently drops a component from the static shell with zero warning. we saw it in LCP metrics before we found it in the code.does
withCacheDebugplay nicely with React'scache()for deduplication, or does the instrumentation layer break the memoization?That wrapper placement one is brutal. Everything looks correct, compiles fine, and just silently does nothing. Definitely one of those you only learn after getting burned once.
The cacheLife('seconds') PPR drop is the same story. We hit it during migration and only noticed it through LCP changes in prod. Feels like something that should be part of every checklist.
On withCacheDebug + React cache(): since the wrapper sits outside the cache boundary, it shouldn’t break deduplication. It just logs executions. Still worth sanity checking with CACHE_DEBUG=true to make sure you’re seeing the expected single execution per render.
The LCP change discovery is the right signal — cache bugs tend to appear in perf metrics before errors log anything. We added
CACHE_DEBUG=trueto our checklist after the same lesson.One thing i'm still unsure about: when
withCacheDebugreports execution count, is that tracking calls before React cache deduplication kicks in, or after? Our numbers felt off on an ISR route and couldn't tell if it was a dedup miss or a misread counter.Good question, I had to double check this myself at one point.
withCacheDebug sits outside 'use cache' in practice. It wraps the raw function and counts every call attempt before Next.js caching comes into play. So you can still see multiple execution logs even when the underlying data function only runs once due to React cache deduplication.
On ISR routes it gets a bit noisier because revalidation cycles can look like repeated executions, when they are actually separate request lifecycles.
What helped me was adding a log inside the actual data function for comparison. If inner logs are lower than withCacheDebug, dedup is working and the wrapper is just counting call attempts. If they match, something in the caching path is not being applied as expected.
the 'count call attempts, not executions' framing clicks for me. we had a case where execution count stayed at 1 but withCacheDebug showed 6 — spent an embarrassing amount of time hunting a bug in the cache config instead of just trusting dedup was working.
the ISR noise thing is the next one to dig into. do you strip revalidation cycles from your comparison, or just accept the count mismatch as expected on ISR paths?
I just accept the ISR noise honestly. Once you know it’s counting call attempts not executions, the mismatch stops being confusing. Reading the wrapper count alongside the inner function logs is more useful than trying to filter anything out.
This is incredibly helpful. Next.js 16’s new
use cachemodel is an amazing architectural step forward, but the "black box" development experience where it silently fails due to simple function wrapping or missing a second argument inrevalidateTaghas been a massive headache. Catching dynamic holes from tightcacheLifewindows before hitting production is a lifesaver for avoiding accidental request-time compilation drops. 👍Exactly. The silent failure on wrapper placement is what got me too. You stare at the code, the directive is right there, and nothing tells you it’s doing nothing. The dynamic hole issue is especially sneaky because the page still works, it just quietly becomes fully dynamic and you only notice when performance drops.
Catching those early, especially with things like tight cacheLife windows or revalidateTag edge cases, saves a lot of production surprises. Really glad it’s been helpful!
Wow! The tool looks helpful for devs struggling with Next.js 16 caching. Thank you for sharing. 👍 Next.js has many types of caching that are hard for me to understand 😅
Thank you! And honestly, you're not alone. The caching model in Next.js 16 is genuinely a lot to wrap your head around at first. If you're finding it confusing, the practical migration guide linked at the bottom might help. It walks through what 'use cache', cacheLife, and cacheTag are actually doing before you start using the toolkit. Hope it helps!
Thank you! 😄 I’ll refer to your documents carefully when using Next.js caching. Your documents seem to be detailed and comprehensive. 👍
This is actually super useful. The biggest issue with Next.js 16 caching right now is the lack of visibility during development, and your debugger solves that really well.
Exactly this. That's what pushed me to build it in the first place. You add the directive, assume it's working, and only find out it isn't when something breaks in prod. Having it surface misses and dynamic holes in real time during dev just saves so much guesswork. Glad it's useful!
nice!
Cool, nice work!
Nice!
the 'keys identical and still firing rapidly' heuristic is the one i was missing. we were comparing inputs and timestamps but not the keys, so the dedup story was invisible.
one extension: we now log the stale indicator alongside the key. identical keys with stale=true plus rapid timestamps usually means the revalidation tag fired but the cache didn't invalidate cleanly. different failure mode, same surface signal.
does your debugger surface the stale status or is that still a manual fetch?