#cache components -> stale router cache

1 messages · Page 1 of 1 (latest)

cloud cargo
#

Hey. So has anyone used cache components?
I'm running into the following issue:

I'm using wordpress as the cms / backend for a project and wpgraphql for queries.
I have a revalidation api endpoint that im using as webhook on the server for revalidation
where i do on demand revalidateTag for a specific cacheTag. I'm using expire 0 as the second param which i thought would be the only thing needed. I also do revalidatePath("/", "layout") there

Then when I build and start. New cms content, refresh. Shows up. -> I navigate to another page -> i click my logo to go home (or browser back nav) -> Stale data (state before latest cms change).

Even subsequent navigations don't solve this. A second refresh IS ALWAYS needed for no stale data anywhere.

Has anyone had a similar experience?

Thing of note. I'm not using updateTag as that can't be used in Route Hanlers.

delicate boughBOT
#

🔎 This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord

🕵️ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize

✅ You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)

bronze pivot
#

Have you tired cacheLife and trying setting stale revalidate and expire all to 0? @cloud cargo

#

Or cacheLife("seconds") seems to have a good profile for up-to-date data

cloud cargo
#

@bronze pivot the issue was more that my '/' route was static because everything was resolvable at build time. This forced SWR scemantics, even though my main point is that I use a route handler for revalidation of tags with expire at 0, and an sse broker that broadcasts updates to connected clients with a router.refresh() on broadcast. That's supposed to clear router cache (i think) but with static homepage it didn't. The solution in my case was to use await connection() (from https://nextjs.org/docs/app/api-reference/functions/connection) . This forces page to be PPR. I don't know if that's the best pattern here, but it seems to fix my issue, and allows me to have cacheLife of hours as a fallback if the webhook fails or something.

bronze pivot
#

If the home page is static, why do we need to revalidate it?

#

It sounds like the data is being fetched on the client making the page static. We need a way of making the hook run again. Revalidate path and revalidate tag and update tag are for refetching data from the server. If you're fetching it from the client, you need to invalidate it on the client somehow or a way of unmounting and remounting the client component. That's fetching the data

#

And ISR but if the page is static there is nothing to ISR because the page is the same every revalidate

#

If you want to use revalidate, you'll have to fetch the data on the server side and pass it to the client

bronze pivot
#

@cloud cargo or you could use something like tanstack useQuery to invalidate on the client

cloud cargo
#

Hey, thanks for the suggestions! Just to clarify, the data isn't fetched on the client at all. Here's how it works:

I have async Server Components that call data functions with "use cache" + cacheTag("events") + cacheLife("hours"). These run entirely on the server. The GraphQL call to WordPress happens server-side, and the result is cached in the server's function cache. The client component (event list) just receives the data as a prop, it never fetches anything itself.
For revalidation I have two steps:

  1. WordPress webhook hits a Route Handler that calls revalidateTag("events", { expire: 0 }) This force-expires the server function cache
  2. An SSE broker broadcasts to connected browsers, and the client calls router.refresh()
    This clears the Router Cache so the browser requests fresh server-rendered content.

The stale data issue I had was because my homepage had no dynamic APIs, so Next.js treated it as a fully static route. Static routes use the Full Route Cache which has SWR semantics. It serves the stale pre-built HTML and revalidates in the background. So even though my function cache was correctly expired by the webhook, the Full Route Cache sat in front of it and kept serving the old page until a second request came in.

await connection() inside a Suspense boundary fixed it because it opts the route into PPR. Now the static shell is served instantly, but the dynamic hole (where the data lives) is reexecuted on every request. That request hits the function cache. If it's been expired by the webhook, it fetches fresh data from WordPress. If not, it serves the cached result. So I get instant page loads + fresh data within seconds of a CMS change, without any polling or client-side fetching.

It's the first time I'm using this paradigm, but I think it works. If my logic has gaps and you'd like to add anything or correct me, I'd be happy to learn

bronze pivot
#

When you tired realdiatePath did you try ("/", "page") instead of layout

#

I use updateTag alot with great success but thats with actions and you need it for a route

cloud cargo
#

No I did ("/", "layout") . The only thing is, I understand this always nuked the entire page cache, and caused a full refetch on everything. I though of this as wasteful. That's why I wanted to move to tags, hence the switch to PPR. Current method seems to work to be fair. Though I think the whole system is more so meant for use with 'server actions' than external data through route handlers, with webhooks etc