Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Route handler always returns X-Vercel-Cache: STALE #65814

Open
joostmeijles opened this issue May 15, 2024 · 16 comments
Open

Route handler always returns X-Vercel-Cache: STALE #65814

joostmeijles opened this issue May 15, 2024 · 16 comments
Labels
bug Issue was opened via the bug report template. Pages Router Related to Pages Router.

Comments

@joostmeijles
Copy link

joostmeijles commented May 15, 2024

Link to the code that reproduces this issue

https://github.com/joostmeijles/route-handler-stale/tree/main

Route handler example: https://github.com/joostmeijles/route-handler-stale/blob/main/app/items/%5Bslug%5D/route.ts

To Reproduce

  1. Start the application in production mode
  2. Visit http://localhost:3000/items/a
  3. Inspect the response headers and verify that first visit has x-nextjs-cache: MISS
  4. Visit http://localhost:3000/items/a
  5. Inspect the response headers and verify that first visit has x-nextjs-cache: HIT
  6. Deploy to Vercel
  7. Visit https://routehandler-stale.vercel.app/items/a
  8. Inspect the response headers and verify that it has always X-Vercel-Cache: STALE

Current vs. Expected behavior

Currently always X-Vercel-Cache: STALE is returned.

Expected is X-Vercel-Cache: MISS on first visit and X-Vercel-Cache: HIT for subsequent visits.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 22.2.0: Fri Nov 11 02:04:44 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T8103
  Available memory (MB): 8192
  Available CPU cores: 8
Binaries:
  Node: 20.12.2
  npm: 10.5.0
  Yarn: N/A
  pnpm: 8.15.7
Relevant Packages:
  next: 14.3.0-canary.63 // Latest available version is detected (14.3.0-canary.63).
  eslint-config-next: N/A
  react: 19.0.0-beta-4508873393-20240430
  react-dom: 19.0.0-beta-4508873393-20240430
  typescript: 5.1.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

App Router

Which stage(s) are affected? (Select all that apply)

Vercel (Deployed)

Additional context

No response

@joostmeijles joostmeijles added the bug Issue was opened via the bug report template. label May 15, 2024
@github-actions github-actions bot added the Pages Router Related to Pages Router. label May 15, 2024
@leerob
Copy link
Member

leerob commented Jun 3, 2024

This behavior is changing with Next.js 15 (where GET Route Handlers are not cached by default).

Would you might updating to the latest https://nextjs.org/blog/next-15-rc and retesting?

@dogfrogfog
Copy link

dogfrogfog commented Jun 4, 2024

Hey @leerob, thanks for you answer.

What if I want to cache GET request?

In my case I have a hCMS with next14.2.3.

The flow is I generate dynamic slug pages at build time, all requests are marked with a tag "CMS-content". And then call revalidateTag to make sure all the pages have a up to date content.

In 99% of cases it works as expected, but in 1% updated pages return X-Vercel-Cache: STALE and never changes. In order to see updates I have to rebuild the project or purge cache.

Maybe you could point me to where the problem could be, since generały it works correct.

❤️🫂

@joostmeijles
Copy link
Author

This behavior is changing with Next.js 15 (where GET Route Handlers are not cached by default).

Would you might updating to the latest https://nextjs.org/blog/next-15-rc and retesting?

Upgraded, but seeing the exact same behavior: joostmeijles/route-handler-stale#1

@leerob
Copy link
Member

leerob commented Jun 4, 2024

For clarity, STALE still means you can serve static content.

The response was served from the edge cache. A background request to the origin server was made to update the content.

Source: https://vercel.com/docs/edge-network/headers#x-vercel-cache

image

Are you wanting to disable the ability to use ISR entirely here? (which is where the background revalidation functionality is coming from).

@joostmeijles
Copy link
Author

Are you wanting to disable the ability to use ISR entirely here? (which is where the background revalidation functionality is coming from).

I want to get the same behavior as for a page based route segment. So only perform the background request when used data changes / is revalidated (for simplicity I left this out of the example).

An example with a page based route segment can be found here: visiting https://routehandler-stale.vercel.app/items-page/a returns a HIT iso STALE (code: https://github.com/joostmeijles/route-handler-stale/blob/main/app/items-page/%5Bslug%5D/page.tsx).

So I would expect that https://github.com/joostmeijles/route-handler-stale/blob/main/app/items/%5Bslug%5D/route.ts results always in HIT (and not STALE) for subsequent requests (when the used data did not change).

@leerob
Copy link
Member

leerob commented Jun 4, 2024

Can you make the deployment link public so I can view it myself? It's currently private.

@joostmeijles
Copy link
Author

https://routehandler-stale.vercel.app/items-page/a is public.

Which link do you mean?

@leerob
Copy link
Member

leerob commented Jun 4, 2024

Thank you. I'm seeing HIT here.

CleanShot 2024-06-04 at 10 39 16@2x

@joostmeijles
Copy link
Author

joostmeijles commented Jun 4, 2024

Exactly. That’s the expected behavior as there is no stale data to revalidate.

For https://routehandler-stale.vercel.app/items/a I would expect the same as there is nothing to revalidate as well.

@leerob
Copy link
Member

leerob commented Jun 5, 2024

It's using ISR for the dynamic route segments. generateStaticParams is not returning any options for the items, so no pages are generated during the build to be statically generated. Instead, the pages are being generated at runtime and then cached. This is why you are seeing STALE first.

  • Do you want to specify the pages to build? Add those into the return value for generateStaticParams
  • Are you wanting other routes to 404 instead of being generated on demand? Use dynamicParams = false (docs)

@joostmeijles
Copy link
Author

I want the route response to be generated at runtime and then cached. Other routes should result in a 404.
In code that would be something like:

export async function GET(
    request: Request,
    { params }: { params: { slug: string } }
) {
    const slug = params.slug;

    // Fetch data for slug
    const res= await fetch(`http://example/{slug}`, next: { tags: `mytag-${slug}`});

    // Returns OK for 'a', 'b', 'c' slugs
    if (res.ok) {
        return NextResponse.json({ slug });
    }

    // Other routes do not have data and should return a 404
    return NextResponse.json({ message: "Not found"}, { status: 404 });
}

export async function generateStaticParams() {
    return [];
}

When visiting https://routehandler-stale.vercel.app/items/a I expect as X-Vercel-Cache header value:

  • 1st visit: MISS
  • next visits: HIT
  • after on-demand revalidate of NextJs tag: STALE

@leerob
Copy link
Member

leerob commented Jun 6, 2024

Got it. As mentioned above, you're then looking for:

export const dynamicParams = false;

https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams

@joostmeijles
Copy link
Author

That works locally, but results in a 404 on Vercel: https://routehandler-stale.vercel.app/items-dynamicparams/a

(code: joostmeijles/route-handler-stale@178aed2)

@leerob
Copy link
Member

leerob commented Jun 6, 2024

That is correct, because they weren't in generateStaticParams.

export async function GET(
    request: Request,
    { params }: { params: { slug: string } }
) {
    let slug = params.slug;
    let res = await fetch(`http://example/{slug}`, next: { tags: `mytag-${slug}`});
    return NextResponse.json({ slug });
}

export async function generateStaticParams() {
    return ['a', 'b', 'c'];
}

@joostmeijles
Copy link
Author

joostmeijles commented Jun 6, 2024

The possible slug params aren't known at build time, so I can't do that.

Please note that specifying slug values when using page.tsx isn't necessary: https://github.com/joostmeijles/route-handler-stale/blob/main/app/items-page/%5Bslug%5D/page.tsx

@leerob
Copy link
Member

leerob commented Jun 6, 2024

If the values are not possible to be known at build time, then there is no way to statically prerender those files. The behavior you are describing for the page should be the same for the route handler. A route handler is basically the lowest level abstraction of a page, which is why they have the same route segment configuration options.

The behavior you are describing where the first visit generates the page, and then the result is cached on the Vercel CDN for subsequent requests, is how both pages and route handlers work. If you are wanting to extend the revalidation period, so you are able to serve cached files longer, you can use the revalidate option.

export const revalidate = 3600;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template. Pages Router Related to Pages Router.
Projects
None yet
Development

No branches or pull requests

3 participants