Repository navigation
Let a "use cache" function return a value without storing it
#97554
Replies: 2 comments 1 reply
This comment was marked as low quality.
This comment was marked as low quality.
|
Hiyo — I do believe something like the snippet at the bottom should work for you. What'll happen here is that, you'd, most likely, initially get the Eventually visiting After 60 seconds, the cache entry is rerun, and that may hit the Once again, eventually, the unstable request succeeds, and we are back on the The one caveat I found here is tag based revalidation. I had initially done However, // app/api/route.ts
import { cacheLife, cacheTag, revalidateTag } from "next/cache";
import { NextResponse } from "next/server";
async function foo() {
"use cache";
const res = await fetch("https://httpbin.dev/unstable?failure_rate=0.9");
cacheTag("unstable");
console.log(res.ok, res.status);
if (!res.ok) {
cacheLife({ expire: 0 });
return { status: "Not OK", timestamp: new Date().getTime() };
}
cacheLife({ revalidate: 60 });
return { status: "All good", timestamp: new Date().getTime() };
}
export async function GET() {
return NextResponse.json(await foo());
}
export async function POST() {
revalidateTag("unstable", { expire: 0 });
return NextResponse.json({ done: "ok" });
}What do you think? I guess you might have already tried these, but found some issue still? |
Uh oh!
There was an error while loading. Please reload this page.
Goals
"use cache"function a way to say "return this result to the caller,but don't cache it", decided after the work is done, since that's often the
only point where you can tell.
result back to the caller.
pushing it out to callers who'd have to guess.
Non-Goals
production. That behaviour is correct and we're not asking for it to change.
to decline.
a function declining its own result.
cacheLife/cacheTagsemantics.Background
We use
"use cache"(withcacheComponents: true, Next.js 16.3.1, Turbopack) tocache proxied GET responses from upstream services. The cached function fetches
upstream and returns the response.
The catch is that whether a response is cacheable is only knowable after the
fetch resolves. We reject a response if it isn't a 200, if it carries
Set-Cookie, ifCache-Controlsaysprivateorno-store, or ifVarymentions
authorizationorcookieall of which mean it's specific to oneclient and must never be served to another.
Today, throwing is the only way a
"use cache"function can signal outward. Thatworks for preventing the cache write, but the thrown value doesn't survive:
So the caller can't tell "this response is fine but not cacheable" from "the fetch
failed." We tried carrying a custom
Errorsubclass through, and separately adeterministic nonce in the message neither survives. Only
err.digestis left,and it isn't attributable back to a specific thrown value without a side channel.
The result is that every rejected-but-successful response costs a second, uncached
fetch just to get the body back to the client. It also double-counts our
request-duration metrics, since both fetches are measured.
Alternatives we considered
needs the response, so this means fetching outside the cache first which costs
every cache hit a wasted fetch instead of every rejection costing one. Strictly
worse, since hits dominate.
err.digest. Not attributable to a specific throwwithout maintaining a side channel, which defeats the point.
cacheLife. Still caches something weexplicitly must not cache, and doesn't return the body.
high. It's currently low enough that the second fetch is an acceptable cost but
it's a cost with no way out, which is why we're raising it.
Minimal reproduction
Run against a production build (
next build && next start) with an upstream thatreturns a non-200. Note that this doesn't reproduce under
next dev, and a mockedunit test won't reproduce it either the redaction only happens in a production
build, which makes it easy to write a "fix" that looks like it works.
Proposal
A call available inside a
"use cache"function that suppresses the cache writefor that invocation while letting the return value through normally:
Semantics we'd expect:
alone rather than overwritten.
cacheLife/cacheTag, is harmless,suppression wins.
"use cache"scope throws, in the same spirit as theexisting cache APIs.
An alternative shape, if a side-effecting call inside the scope is unappealing:
allow the function to return a wrapper the runtime unwraps, e.g.
return dontCache(value). We'd be happy with either; the call reads better at thepoint of decision.
Contributing: Yes, I'd like to implement this. I'm familiar with the caller
side of
"use cache"but not with the internals that decide whether a cache entrygets written, so a pointer to where the write happens and which tests to extend
would get me moving. I'm also happy to defer entirely on the API name and shape; if
maintainers prefer the wrapper form, or a different name, I'll build whichever you
land on.
All reactions