GET route handlers that use dynamic/runtime APIs (cookies, request body parsing, database writes) and return NextResponse.redirect() are being served with cache-control: max-age=86400 by the Netlify CDN. This causes all visitors hitting the same URL to receive the same cached redirect, even though the handler produces a unique redirect target on every invocation.
Environment
- Next.js 16 with
cacheComponents: true
- App Router
output: 'standalone'
- OpenNext Netlify adapter (auto-detected, not pinned)
- Deployed on Netlify
Reproduction
I have a route handler at app/(interview)/onboard/[protocolId]/route.ts that:
- Reads
cookies()
- Parses the request body or search params
- Makes a database write (creates a new record via Prisma)
- Redirects to a URL containing the newly created record's ID
// simplified version of the handler
export async function GET(req: NextRequest, { params }) {
const { protocolId } = await params;
// Dynamic APIs used:
const cookieStore = await cookies(); // runtime
const searchParams = req.nextUrl.searchParams; // runtime
// Database write - produces a unique ID every time
const { createdInterviewId } = await createInterview({ protocolId });
const url = new URL(req.nextUrl);
url.pathname = `/interview/${createdInterviewId}`;
return NextResponse.redirect(url);
}
None of this should be cacheable. Next.js correctly identifies the route as dynamic (the handler accesses cookies(), reads from the request object, and performs a database write). However, after the first request hits Netlify, subsequent requests to the same /onboard/[protocolId] URL receive a cached 307 redirect pointing to the first interview ID that was created, rather than triggering a new handler invocation.
Inspecting the response headers on the cached response shows:
cache-control: max-age=86400
This 24-hour TTL is not being set by our application code, and does not correspond to any Next.js default that I'm aware of. It appears to be injected by the adapter or the Netlify CDN layer.
Important: This issue does not occur on Vercel. Only netlify with open-next
Workaround
Explicitly setting Cache-Control: no-cache, no-store, must-revalidate on the redirect response resolves the issue:
return NextResponse.redirect(url, {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
});
Expected Behaviour
Route handlers that Next.js identifies as dynamic (because they use runtime APIs like cookies(), read from the request object, perform database operations, etc.) should not have their responses cached at the CDN layer. The adapter should either:
- Omit CDN cache headers entirely for dynamic route handler responses, or
- Explicitly set
Netlify-CDN-Cache-Control: no-store for responses from dynamic routes
This seems to be the same class of problem reported by the Clerk team ([forum thread](https://answers.netlify.com/t/cache-handling-recommendation-for-authentication-handshake-redirects/143969)), where auth handshake redirects were also being cached by the Netlify CDN when served through Next.js on OpenNext.
Questions
- Where is the
max-age=86400 value originating? It's not a value I can find in the Next.js source or Netlify's documented defaults for function responses.
- Is the adapter meant to be translating Next.js's Full Route Cache semantics into CDN cache headers for route handlers? If so, it seems like the dynamic detection isn't being propagated correctly for redirect responses.
GET route handlers that use dynamic/runtime APIs (cookies, request body parsing, database writes) and return
NextResponse.redirect()are being served withcache-control: max-age=86400by the Netlify CDN. This causes all visitors hitting the same URL to receive the same cached redirect, even though the handler produces a unique redirect target on every invocation.Environment
cacheComponents: trueoutput: 'standalone'Reproduction
I have a route handler at
app/(interview)/onboard/[protocolId]/route.tsthat:cookies()None of this should be cacheable. Next.js correctly identifies the route as dynamic (the handler accesses
cookies(), reads from the request object, and performs a database write). However, after the first request hits Netlify, subsequent requests to the same/onboard/[protocolId]URL receive a cached 307 redirect pointing to the first interview ID that was created, rather than triggering a new handler invocation.Inspecting the response headers on the cached response shows:
This 24-hour TTL is not being set by our application code, and does not correspond to any Next.js default that I'm aware of. It appears to be injected by the adapter or the Netlify CDN layer.
Important: This issue does not occur on Vercel. Only netlify with open-next
Workaround
Explicitly setting
Cache-Control: no-cache, no-store, must-revalidateon the redirect response resolves the issue:Expected Behaviour
Route handlers that Next.js identifies as dynamic (because they use runtime APIs like
cookies(), read from the request object, perform database operations, etc.) should not have their responses cached at the CDN layer. The adapter should either:Netlify-CDN-Cache-Control: no-storefor responses from dynamic routesThis seems to be the same class of problem reported by the Clerk team ([forum thread](https://answers.netlify.com/t/cache-handling-recommendation-for-authentication-handshake-redirects/143969)), where auth handshake redirects were also being cached by the Netlify CDN when served through Next.js on OpenNext.
Questions
max-age=86400value originating? It's not a value I can find in the Next.js source or Netlify's documented defaults for function responses.