Allow request APIs inside "use cache: private" in Route Handlers #98549
henryjayyu
started this conversation in
Ideas
Replies: 1 comment
|
Hi, a little hard to read through the wall of text. As far as I can understand, you are asking for: import { cookies } from "next/headers";
async function readSessionPrivate() {
"use cache: private";
const c = await cookies();
return c.get("session")?.value ?? null;
}
export async function GET() {
// Simulate a service layer that resolves the caller twice.
// With request-scoped memoization, we'd expect one read.
const a = await readSessionPrivate();
const b = await readSessionPrivate();
return Response.json({ a, b, equal: a === b });
}Are you claiming that this gives an error? It works just fine for me in 16.3.4 What have you tried, and how have you tried it. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Goals
cookies(),headers(),searchParams) inside a'use cache: private'scope in Route Handlers, the way they are already allowed in Server Components, instead of throwingE178.TODO.Non-Goals
"use cache"semantics. It should keep rejecting request APIs everywhere; that restriction is correct.cache()work outside the component tree. That is a different ask, raised in Allow react cache to work on route handle and server actions #53489.cacheLife/cacheTagsemantics.Background
Route Handlers have no supported request-scoped memoization today.
The natural reach is React
cache(), and it is inert there. React's own caveats saycache"is for use in Server Components only," and that calling a memoized function outside a component "will still evaluate the function but not read or update the cache." The Next.js caching docs agree: memoization "doesn't apply tofetchrequests in Route Handlers as they are not a part of the React component tree." Consistent with that,packages/next/src/server/route-modules/app-route/module.tsdoes not reference React at all — there is no dispatcher forcache()to find, so it silently degrades to a pass-through with no warning.Worth flagging separately: the Authentication guide defines
export const verifySession = cache(async () => {...})and then says you can invoke it "in your data requests, Server Actions, Route Handlers", with a Route Handler example callingawait verifySession(). Theafter()docs similarly say "You can use Reactcacheto deduplicate functions called insideafter." Both point users at something that no-ops in a Route Handler. That is a docs bug worth fixing regardless of what happens with this proposal.The cost is not theoretical. On one app on 16.3.4, an auth wrapper resolves the caller once, and then every service-layer call inside the handler re-resolves it because the
cache()wrapper does nothing. Counted by driving the real route modules: a singleGETperformed 3 session decrypts and 3 identical permission-user database queries where one of each was needed. Every auth SDK I checked (Auth.js v5,@auth0/nextjs-auth0v4,@supabase/ssr, Clerk'sauth()) re-resolves per call and relies on the caller not calling twice — so this multiplies quietly across any app with a service layer."use cache: private"reads like exactly the right primitive. Its docs describe request-scoped reuse — "In production, matching calls within one request can reuse the same result, but Next.js does not store it in a server cache across requests" — listcookies(),headers(), andsearchParamsas allowed, and explicitly name this use case: "If a function only needs request-scoped deduplication, usecacheLife({ stale: Infinity })to keep it from lowering the route's stale time."But it throws in a Route Handler, and the implementation carries the open question:
(Still present on
main; verified asE178in a 16.3.4 install.)Alternatives, and why they don't cover this.
unstable_cacheand"use cache"are cross-request and reject request APIs by design, and cache keys and tags are stored in plain text — inappropriate for anything session-derived. TheE178guidance to "read and resolve request data outside the cached function, then pass only the values you need as arguments" is the thing"use cache: private"exists to avoid. That leaves wrapping the handler in anAsyncLocalStoragescope — which is what Clerk ships, what Next.js runs on internally, and what theafter()docs demonstrate for per-request context — or threading the value by hand. Both are workarounds for something the platform nearly supports already.Proposal
Answer the
TODOin the affirmative: allow runtime request APIs inside a'use cache: private'scope in Route Handlers, matching Server Component behaviour, and keep throwing for'cache'and'unstable-cache'.The safety argument for the existing restriction is that request-derived values must not leak into a cache shared across requests. That argument does not apply to
private-cache, which by definition is not stored across requests — which is presumably why the same access is already permitted in Server Components.Two smaller things that would help either way:
"use cache: private"page could state that Route Handlers are supported; it currently doesn't mention them in either direction, so today the only way to learn the answer is to hitE178or readmodule.ts.after()docs should stop recommendingcache()for Route Handlers, since it silently does nothing there.Happy to validate a canary against a real application with a service layer and measure the before/after, if that's useful.
All reactions