Next.js Discord

Discord Forum

Can you cache full api route response?

Answered
Irish Red and White Setter posted this in #help-forum
Open in Discord
Irish Red and White SetterOP
Fetch calls within an api route can use the NextJS data cache, and that's great.

But I wonder if it's possible within the confines of NextJS itself to cache the whole response.

Let's say you make a fetch call and then do some expensive computations on that data which you want to keep cached for an hour, is that possible?

I know you can set up your own redis cache or something else, or even put an nginx proxy in front to handle caching. But is there a way with NextJS to do this instead?

Like manually using the data cache?
Answered by "use php"
View full answer

41 Replies

@B33fb0n3 yes, you can add a tsx export const dynamic = 'force-static' To your route and like the the whole response will be cached
Irish Red and White SetterOP
It seems if you use that approach you can't revalidate that content with ISR, using revalidatePath.

Is that expected?
@Irish Red and White Setter It seems if you use that approach you can't revalidate that content with ISR, using revalidatePath. Is that expected?
you can revalidate the path as well as the tag (if you using any tags). Like that the whole page will be revalidated & reloaded (if open) and rebuild in the background
@B33fb0n3 you can revalidate the path as well as the tag (if you using any tags). Like that the whole page will be revalidated & reloaded (if open) and rebuild in the background
Irish Red and White SetterOP
Do you mean regular pages that the user can visit? My use case is an api route, I am fetching a mobile menu structure, /api/getmobilemenu, it's just an api route.

Maybe they behave the same basically though. But I can't revalidate an api route that is static. No matter if I call revalidatePath or revalidateTag.
I have also tried to have the fetch calls inside that route to be tagged, and then revalidateTag on that specific tag. But that does not clear the cache either.

Once a route is built with force-static it seems to be stuck that way and cannot be ISR:d?
I interpret the documentation otherwise, which has me confused.
Irish Red and White SetterOP
@B33fb0n3
sorry I just noticed something, it seems to be a difference if requesting from my browser instead of a rest tool, like POSTMAN or Thunder client
now it refreshes the content

I guess it is dependent on some cache control headers from the client requesting?
Irish Red and White SetterOP
@B33fb0n3 hmm well actually it only worked if I had devtools open with no cache checked, meaning my request sends "Cache-Control: no-cache"

But I can't expect visitors to my site to add that header to my request. They are not developers, and wont know when the content is stale. Tricky!
@B33fb0n3 yea, did you test this? https://discord.com/channels/752553802359505017/1286338763319939102/1286608934198575166
Irish Red and White SetterOP
Yes, seems like time based revalidation works as expected. Subsequent requests after elapsed time defined will net in a new dynamic request being performed and then cached for the new period.
@Irish Red and White Setter Yes, seems like time based revalidation works as expected. Subsequent requests after elapsed time defined will net in a new dynamic request being performed and then cached for the new period.
that sounds great. For me it looks like that's the only revalidation that you can currently make
@B33fb0n3 that sounds great. For me it looks like that's the only revalidation that you can currently make
Irish Red and White SetterOP
I know revalidateTag is to be used on what fetches you have tagged.

In my scenario, my route has the uri of /api/getmobilemenu - I was thinking I should be able to do either revalidateTag('/api/getmobilemenu') or revalidatePath('/api/getmobilemenu') to clear that cache. I will continue to investigate.
Irish Red and White SetterOP
I've even tried asking ChatGPT, the desperation rises
Irish Red and White SetterOP
@B33fb0n3 If you have expensive calculations it seems you can use unstable_cache to store these (although this is unstable as it indicates).
However you still have to make sure your api route then is NOT static, so you lose that performance benefit sadly.
Irish Red and White SetterOP
I've figured out the correct terminology for my problem as I see it now.

I am using a "route handler", as in NOT a React rendered component or tree, but simply a handler for a GET request.
These are said to NOT be cached in the "Full route cache" by default, but CAN be, by export const dynamic = "force-static". However, according to my testing with running this in prod mode, if a "route handler" is a GET request it seems to be cached in the full route cache even if you do not have the dynamic property set. Its response calculated at build time and never revalidated again (unless you use time based revalidation).

The issue at hand is that "route handlers" can only be "full route cache" revalidated by a revalidate time duration, NOT by on demand ISR.
On demand ISR can only revalidate paths that relate to rendered components, or tags that related to your underlying data fetches.

Using revalidateTag on data that is used in a fetch within a rendered component with its own URL, will actually clear that that URL from the full route cache.
But the same does not seem to apply if that URL is in fact a "route handler". Thus, neither revalidatePath or revalidateTag is able to clear the full route cache entry for the response from a route handler.

The workaround is to never cache these. The drawback is that you do not get as good performance as every single request must be processed by NextJS instead of immediately return from the built in router.

@"use php" You mentioned the documentation, I've read that now and also the pages on ISR and route handlers. Would you say my conclusion is accurate, or do you know of a way to clear the cache of a route handler on demand?
yes, use revalidatePath or revalidateTag to clear on demand
@"use php" yes, use revalidatePath or revalidateTag to clear on demand
Irish Red and White SetterOP
But what am I supposed to define as the value for the path or tag?

Given my route handler is /api/test, would you call revalidatePath('/api/test') ?
I've tried that, and revalidateTag as well, but this does not clear the response. I'm on Next 14.2.5, maybe that's the issue.
where are you running revalidatePath, Its supposed to run on server.
Irish Red and White SetterOP
Yes, in a separate route handler:

/api/revalidate/route.ts
"use server";

import { revalidatePath, revalidateTag } from "next/cache";

export async function GET() {
  console.log("INVALIDATE CACHE");

  revalidateTag("/api/test");
  revalidateTag("/api/test/");
  revalidateTag("api/test");
  revalidateTag("api/test/");

  revalidatePath("/api/test");
  revalidatePath("/api/test/");
  revalidatePath("api/test");
  revalidatePath("api/test/");

  return Response.json({ message: "Hello from revalidate" });
}


Calling this endpoint works fine, and I see the console log output. However, it does not seem to affect anything cached.
Above illustrates I've tried the permutations I can think of for the path value, it's my goal to only have to call one variant obviously 🙂
@"use php"
can you try to await it?
Otherwise I'll soon give you a repo with example
Irish Red and White SetterOP
absolutely, I'll try that
@"use php" No difference.
Intellisense in VSCode also warns that await has no effect on those functions, so I guess they're not async by nature.
i'll give a min repro repo soon
tomakeitwork
Irish Red and White SetterOP
🙏 many thanks, will be greatly appreciated
Irish Red and White SetterOP
Idea for a somewhat silly workaround.. if this capability is blocked on route handlers, maybe one could use a regular page instead. As in render a React component instead of just a api response.
I need JSON in this example, so I'd have to output json as a raw string in html, and then use regex or something to cut that out from the rendered html.

It could work!
I'll send a github link of sample app soon
Irish Red and White SetterOP
Yup, that's the documentation for the data cache. That's like the layer below the full route cache, which is what things like dynamic = "force-static" and similar pertains to.
Answer
Irish Red and White SetterOP
@"use php" I think the key was your comment here:

  // This will be logged on every request(if uncommented).
  // console.log("full cache route GET");


So my aim was to cache the full response, like it is cached for example when using force-static. Basically the "Full route cache".
But, as we can see, it is not possible and probably not within the design principles of NextJS. It certainly had me confused, as there is such an intricate system of caching that it seems that it would be logical to follow along the whole static vs dynamic + ISR on demand approach.

However, as you show we have the unstable cache for any expensive computations. At least from testing locally, the response time for such a cached response is still extremely fast, so definitely good enough 👍
Irish Red and White SetterOP
In summary for anyone else reading this later:
It is not possible utilizing the full route cache, but you can do the next best thing to move your full response into unstable_cache, see example in marked solution.