Skip to content

Dynamic route handler redirects cached with max-age=86400 #3460

Description

@jthrilly

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:

  1. Reads cookies()
  2. Parses the request body or search params
  3. Makes a database write (creates a new record via Prisma)
  4. 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

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions