next.js: [NEXT-1186] revalidatePath not working for dynamic routes while res.revalidate works fine
Verify canary release
- I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: darwin
Arch: x64
Version: Darwin Kernel Version 22.5.0: Mon Apr 24 20:51:50 PDT 2023; root:xnu-8796.121.2~5/RELEASE_X86_64
Binaries:
Node: 16.17.1
npm: 8.19.2
Yarn: N/A
pnpm: N/A
Relevant packages:
next: 13.4.7-canary.1
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0
typescript: 5.1.3
Which area(s) of Next.js are affected? (leave empty if unsure)
App directory (appDir: true)
Link to the code that reproduces this issue
https://github.com/roxizhauk/revalidate-path CodeSandBox
To Reproduce
Visit /blog/[anything]
(e.g. /blog/test
) to generate a dynamic page and check the time shown
To try “unsuccessful” app directory’s revalidatePath
:
- hit
/api/revalidate-path?path=/blog/[anything]
and you’ll see “revalidated” - refresh
/blog/[anything]
and you’ll see the time not changed
To try “successful” pages directory’s res.revalidate
:
- hit
/api/revalidate?path=/blog/[anything]
and you’ll see “revalidated” - refresh
/blog/[anything]
and you’ll see the time changed
Describe the Bug
The app directory’s revalidatePath
works fine for “/” or “/blog” but dynamic routes like “/blog/1” or “/blog/test” while pages directory’s res.revalidate
works fine for all
Expected Behavior
revalidatePath
works the same as res.revalidate
for dynamic routes
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
From SyncLinear.com | NEXT-1186
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 29
- Comments: 76 (12 by maintainers)
Commits related to this issue
- Update docs for revalidatePath fix (#55083) This updates docs for the fixes landed in https://github.com/vercel/next.js/pull/53321 related to `revalidatePath`. Fixes: https://github.com/vercel/n... — committed to vercel/next.js by ijjk 10 months ago
- Fix notFound status code with ISR in app (#55542) This ensures we properly set/restore the status code with ISR paths in app router so that when we set the 404 status code with `notFound` it is per... — committed to vercel/next.js by ijjk 9 months ago
- Add route groups example to revalidatePath doc (#55543) Adds explicit examples for route groups as those may not be obvious from existing examples. x-ref: https://github.com/vercel/next.js/issue... — committed to vercel/next.js by ijjk 9 months ago
Hi, the
revalidatePath()
behavior has now been fixed to correctly revalidate specific URLs instead of just page paths inv13.4.20-canary.27
of Next.js, please update and give it a try!An example of the issue that was fixed here, when you only want to revalidate
/blog/post-1
you can now pass that torevalidatePath
instead of having to invalidate all paths for/blog/[slug]/page
.Okay - Seems like we can use
unstable_cache
to achieve the same functionality as fetch tag invalidation, but for any type of data fetching.Then you can invalidate the post by invalidating the postId as a tag like so:
revalidateTag(postId)
At the moment
res.revalidate()
can achieve this for ISR paths, we are investigating this behavior forrevalidatePath()
though.I can confirm that I’ve lost brain cells on this as well. Recap of the above posts in this thread is that right now is that there is no way to bust cache for a specific page.
revalidatePath
only works with “router paths” and not URIs, like the old one worked. Meaning/test
and/test/[slug]
work but/test/1
doesn’t. The caveat being, of course, that/test/[slug]
is a wildcard revalidate which ain’t very useful.Tagging works as an alternative, but it is only supported by next’s
fetch
, meaning that we can’t tag stuff freely. Some people have usedunstable_cache
function to tag async calls. The caveat being that it is meant for cache control over individual async actions, not whole pages, meaning we have to invent tag naming conventions ourselves.There is no clear answer on how
generateStaticParams
cache is handled. We have confirmed thatunstable_cache
with tags doesn’t seem to do anything withingenerateStaticParams
, probably due to the way the router works. @jwalcherIf one is brave or desparate and still choses to do the tagging, I would say it is in fact the most correct way of doing this as it is basically what
revalidatePath
is doing as well. Unfortunately it is not a stabile API, it is entirely undocumented, not pretty to look at, and very implicit in usage, and finally, it doesn’t just invalidate the tag - it caches the data, meaning it performs data transformations, and there are other GithHub issues open about those being inaccurate. 😭For reference, for future readers, attaching an example of how I do that currently:
So, there effectively is no way to exert control over cache invalidation. Which was supposed to be the strongest point of the App router performance…
Note: we are investigating this behavior more and will add updates here
I stumbled upon Vercel’s take on how to do this with tags 😃 https://github.com/vercel/commerce/blob/main/app/api/revalidate/route.ts
@leerob The issue isn’t that we don’t understand the intent of the approach, instead it is the cardinality of it - it isn’t very sane to revalidate 7000 product pages every time any product changes. We need to be more granular with this, and the old uri-based API allowed that out of the box. Now, we either have to make it ourselves, which is a pita, or use
pages
router for this singular purpose.I agree that the tags approach is healthier in the long run, and able to tightly integrate cache busting with next. Its just a pain to write granular tagging mechanisms ourselves, that is all. So I’m kinda hoping to see a package/utility pop up that would offer a better devex when using tagging.
Is there any possibility of adding tag as a const in the page functions. So instead of cache tags only working in fetch requests, it could work for any type of data fetching (E.g. using Prisma)
Example if we have
app/[id]/page.tsx
That way, we could invalidate whichever data fetching strategy is being used (be it
fetch
or directly from database or whichever)The problem is still here. But in a smaller effect.
I’ve been testing
revalidatePath
with different parameters on v13.5.3. For example,revalidatePath("/(main)/cases/[slug]", "page")
worked fine.But when I tried to
it worked only when I was refreshing and staying on the exact same page. Coming from the other page or switching to other page made the cache stale and I could not revalidate anymore. So I had to rebuild the whole project. The bad thing that it’s hard to figure out visiting which page makes the cache impossible to revalidate.
I can revalidate the page just putting the url in the browser. It works many times in a row. But when I start to go between the pages, at some point revalidation stops working.
I am using
res.revalidate
approach frompages
router which at least works. I could not understand at which point App routerrevalidatePath
stops working. The issue is still there when trying to revalidate a specific path from the dynamic route.This is actually pretty critical - Essentially the previous
res.revalidate
supported this functionality.To allow for dynamic revalidation, I tried to use a
/pages/api
endpoint to revalidate a dynamic path located in my app directory, however, this also fails because (https://github.com/vercel/next.js/issues/48771)res.revalidate
requires getStaticProps, which the app directory does not use anymore.This means that effectively there’s no way to revalidate dynamic paths in the app directory. You can use the tag revalidation, but that is only compatible with
fetch
. Use cases where data is fetched directly from a database can therefore not make use of on-demand revalidation for dynamic paths.This means that we have two options: a) Move all routes to the pages directory or b) Use a sub-optimal revalidation strategy where you (e.g.) invalidate ALL blogpost [ids] versus just id 1 when id 1 changes.
Both seem sub-optimal
We are experiencing the same issue. Using the examples above:
App Router
revalidatePath("/blog/[id]")
-> works locally (pnpm build && pnpm start), but does not work on Vercel preview or production deployments.revalidatePath("/blog/first")
-> does not workPages Router
await res.revalidate("/blog/first");
-> works locally and on VercelThere’s no way to trigger a revalidation of a specific dynamic route?
same here, revalidatePath() for dynamic routes not working
using “next”: “13.5.4”
This issue still persists
I recently implemented tagging all
fetches
and callingrevalidateTag
via webhooks from my CMS, to get a fine-grained revalidation at the level of individual pages. However, leaving aside the extra work, I would still argue that at the moment it does not work according to specs, as documented in https://nextjs.org/docs/app/building-your-application/caching#data-cacheNamely, in production, after
revalidateTag
is executed on the server, we need two client requests to see the new content. According to the docs, the cache should be purged immediately, and the new content show on the first client request. (In dev-mode, one request is indeed sufficient.)If anyone can confirm this diagnosis, we should perhaps file a new bug.
Sorry if this wasn’t clear – Route Handlers are
revalidate
are stable. I’m referring your comment aboutunstable_cache()
.This is what
unstable_cache()
, when stable, will do!@jwalcher to be clear, you are referring to an unstable API that will change and should not yet be adopted. Unstable features are not published to the documentation for this reason.
@edjust please open a separate issue with a reproduction 🙏
I don’t know if I’d say we “glossed over it” – it’s documented! But we are working to make the documentation better and add diagrams to help explains concepts. That should be landing shortly https://github.com/vercel/next.js/pull/52514.
Experiencing the same issue as well.
revalidatePath
doesn’t seem to be working correctly. The only page/route I’m able to revalidate seems to be the root/
. Revalidate calls to dynamic routes such asposts/post-1
don’t seem to be working.Using version:
13.5.4
@leerob oh, I wasn’t aware that it was unstable, that changes things a lot and I’m sorry for saying “glossed over”. I was under the impression that both Route Handlers and
revalidate
are stable. Probably as there is no warning on their docs saying they are unstable 😕In any case, I don’t think there is a misunderstanding of diagrams here, its purely that a major piece of old functionality is missing entirely.
@jwalcher Absolutely agree. It is kind of weird that Next, a framework known for championing caching stuff into the mainstream, released a version that just glosses over the whole caching thing, in a release that was supposed to be all about caching 😄
How is it working for you right now? Via pages router? Can you tell us a bit more please, I would find it very useful, as am sure will others.
I guess you are using the pages router??
We have a website coupled to a headless cms and almost all of our pages are in a catchall route. app/[[…slug]]. Some are static prebuild with generateStaticParams. With revalidatePath it looks like I can only revalidate /[[…slug]] , which would basically invalidate almost every page in the website. This is working, however it seems to me if we could invalidate a single page this would be more efficient?
Same here. Any
revalidatePath
calls to dynamic routes such asrevalidatePath("posts/1")
don’t seem to be working as expected.Using version:
13.5.4
@DennieMello thanks for the flag, opened https://github.com/vercel/next.js/pull/55542 and https://github.com/vercel/next.js/pull/55543 addressing those.
@leerob to follow up on @markomitranic – the issue for me is that the development of the (as you say) unstable
revalidatePath/revalidateTag
API has broken the oldres.revalidate()
(at least for me). So I’m a bit stuck between a rock and a hard place here. Otherwise happy to wait!Yes, I have a route handler in the pages directory calling res.revalidate on individual routes which themselves are in the app directory. I even had a workaround to revalidate also in dev mode by simply clearing the cache altogether
This strategy was working fine until v13.3, at which point some changes introduced in the Next server led to interference with my middleware, see https://github.com/vercel/next.js/issues/50464 Now I could work around the middleware, but my hack to revalidate in dev mode definitely does not work anymore starting in v13.5 or so. This all sounds convoluted, and it is, but my feeling is that the issues (with the old method) would be fairly easy to resolve if somebody cared to look. Alternatively, I’d be happy to wait if there was a clear statement about what the plans are to finally address the issues that @markomitranic you so well summarized.
Ask general questions somewhere like the community Discord - not this Issue which is about a specific topic.
@roxizhauk I would say yes - always good to have an updated reproduction with the latest version.
If you could make a demo on CodeSandbox or StackBlitz this can also make it easier for others to set up the environment to see the problem, confirm the behavior and try out fixes
@levipadre The way I handled this was with a tag in the fetch with the slug of the page.
With your fetch tagged, you can pass that as the
url
parameter to the revalidate API call.I worked with another dev on the project to discover this, so if you’re interested in why it works this way:
Firstly, it’s worth noting that
revalidatePath
actually just callsrevalidateTag
(file here). So rather than differentiating between them, let’s stick withtag
for everything.Next is caching your fetch responses on the filesystem inside
.next/cache/fetch-cache
. There are a bunch of generated JSON files which are the fetch responses themselves, and then a manifest which maps a tag to a JSON filenameChecking this manifest, you’ll see that it’s not actually caching against the slugs of your pages, but against the filesystem path (the one with dynamic segements in it). As an example, here’s an excerpt from mine:
Calling revalidate against a specific slug (like
/my-page
) won’t revalidate anything because that key isn’t in the manifest.You can see there are various keys under a dynamic segment route, and these relate to how many slugs have been hit for that route. E.g. my first key is for
/en/my-page
and the second for/en/my-second-page
.I could revalidate
/[locale]/[[...slug]]/page
, but then every single one of my pages’ fetch calls will be revalidated, which isn’t ideal when you just want to do one page. That’s why adding a custom tag and revalidating against that worked well for us.Hi, I ended up here while looking for a way to provoke hard navigate:
/response/1234/sections/1
/response/[responseId]/layout.tsx
loads the right response and put it into a client context. The layout uses “force-dynamic”.So I changed the
router.push
towindow.location.href
to provoke a refresh but that’s not very good. I might instead create an endpoint to get fresh data from the client but that kinda defeats the purpose of the RSC.Is this what you tried to achieve @roxizhauk?
All those
revalidate
method seems to target static/ISR, to invalidate the server cache, but I struggle to find a solution to provoke hard navigates client-side after an update, telling torouter.push("/response/1234")
that response “1234” has been modified since the page was loaded.Found the issue in my code @khristovv. My UI was not updating correctly, however if I logged on the server whatever is returned from the serverAction, it was updating correctly. Try to log the returned value from your API and see if it changes correctly, if it does then revalidatePath() is working well, the issue might be somewhere else.
I am using a dynamic route /dashboard/properties/[id]
1 - Getting the
propertyDetails
in a async server component.propertyDetails = await fetchProperty({ propertyId: params.id });
2- passing the details from server component to -> client component and calling the
updatePropertyDetails
withrevalidatePath
from the client component.3- The UI now is updating correctly as long as I only use the data coming directly from the
propertyDetails
from the server component, without changing the dataScrap my old comment, seems to be working correctly, my mistake was assigning the propertyDetails to a new variable in the client component, try to use the return value directly
For point 2, there are these issues related to
TypeError: fetch failed
, maybe related:Thank you! I confirm that now both updating the cache of one page and updating the cache of the entire route works, using the second argument “page”. I just suggest adding to the documentation a variation using routing groups. If you need to clear the entire route segment - revalidatePath(“/(main)/post/[slug]”,“page”). This is not very obvious, given that to clear one page you need to set evalidatePath("/post/post1).
I’m really glad this problem is finally fixed. It would also be great if you could help bring the developers’ attention to another cache-related issue that has been active for over 6 months. If the page has dynamic parameters, the 404 error is returned only the first time the page is rendered in production. All the following responses return a 200 response. #43831 #48342 #51021 #45801
What I currently understand (though it’s merely a re-phrase of https://github.com/vercel/next.js/issues/49387#issuecomment-1552394378) is:
revalidatePath
works for/blog
or ex./blog/[id]
to revalidate all segments under/blog
res.revalidate
works for/blog/[a-specific-id]
to revalidate the individual pageSo I guess we just use them separately for now until they deprecate
pages/
I’m using 13.4.19 and revalidate path doesn’t work for nested routes however my revalidate route handler return 200. @leerob is this api stable? Thanks
Operating System: Platform: darwin Arch: x64 Version: Darwin Kernel Version 21.6.0: Thu Sep 29 20:12:57 PDT 2022; root:xnu-8020.240.7~1/RELEASE_X86_64 Binaries: Node: 18.13.0 npm: 8.19.3 Yarn: 1.22.10 pnpm: 8.3.1 Relevant packages: next: 13.3.0 eslint-config-next: 13.3.0 react: 18.2.0 react-dom: 18.2.0
My Next.js project is not functioning properly in the production environment (Firebase Hosting). When I run the
build
,export
, andstart
commands locally on my machine, everything works fine. However, I’m encountering issues with the revalidate and fallback features not working as expected in the production environment.I have already updated my Next.js version to 13.3.0 based on this discussion https://github.com/vercel/next.js/discussions/42290#discussioncomment-5502200, but the issue persists.
I expect the revalidate and fallback features to work correctly in the production environment as they do on my local machine.
Actual Behavior: In the production environment, the revalidate and fallback features are not functioning as expected. The pages do not update automatically with the specified revalidate interval, and the fallback behavior is not working with new params that wasn’t specified initially.
My page is /profile/[id].tsx
Sorry if I am misunderstanding, but are you now calling
revalidatePath
on dynamically generated routes/posts/1
,/posts/2
, etc. or do we still need to put the path/posts/[id]
Also, a general question about ISR: How do people deal with deleting specific routes when the content is deleted from CMS?
@roxizhauk from your reproduction steps it seems you are not calling
revalidatePath()
with the correct pathname as it needs to math the name of the page on your filesystem e.g.app/blog/[id]/page
it should berevalidatePath(/blog/[id])
notrevalidatePath(/blog/first)
Related response here https://github.com/vercel/next.js/issues/49778#issuecomment-1547028830
https://github.com/vercel/next.js/issues/49778#issuecomment-1546968234