Summary
revalidatePath('/parent') does not invalidate cached fetch() results rendered inside child routes (e.g. /parent/child). After #917 lands, revalidatePath() invalidates fetches rendered at the exact path, but the layout-chain case is still broken.
Background
#917 added route-scoped implicit tags as soft tags on App Router fetch reads. The generated entry calls:
setCurrentFetchSoftTags(__pageCacheTags(cleanPathname, []));
The second argument is the layout-segment list, hardcoded to [] at every call site. As a result, the only soft tag a render carries is the page-level path tag (_N_T_/parent/child).
Next.js derives a chain of layout tags in addition to the page tag — see getImplicitTags. For /parent/child, Next.js attaches roughly:
_N_T_/layout (root)
_N_T_/parent/layout
_N_T_/parent/child/layout (if present)
_N_T_/parent/child (page)
revalidatePath('/parent') writes the _N_T_/parent marker, which matches the layout tag carried by every child render — that's what makes parent-path revalidation propagate.
Repro
- App with
/parent/page.tsx and /parent/child/page.tsx, both calling fetch(url, { next: { revalidate: 3600 } }) (no explicit tags).
- Render
/parent/child to populate the fetch cache.
- Trigger
revalidatePath('/parent') from a server action or route handler.
- Re-render
/parent/child.
Expected: the fetch in /parent/child re-issues to the origin.
Actual: the cached fetch result is reused.
The exact-path case (revalidatePath('/parent/child') then re-rendering /parent/child) works correctly post-#917.
Why this matters
Users who test path revalidation at the page level will see it work and assume the nested case works too. The failure mode is silent stale data — exactly the bug revalidatePath() is supposed to prevent.
Status
Not a regression — the nested case was also broken before #917 (where revalidatePath() invalidated zero soft-tagged fetches). #917 is a strict improvement; this issue tracks the remaining gap.
Proposed fix sketch
- Build the layout-segment list for the matched route at codegen time (the route trie already knows the layout chain) and pass it as the second argument to
__pageCacheTags.
- Or compute the layout chain at request time from
cleanPathname and inject it into setCurrentFetchSoftTags alongside the page tag.
Either way, the fix is contained to the App Router entry template + __pageCacheTags callers; the cache-handler side already treats soft tags as read-time misses without persisting them on entries.
References
Summary
revalidatePath('/parent')does not invalidate cachedfetch()results rendered inside child routes (e.g./parent/child). After #917 lands,revalidatePath()invalidates fetches rendered at the exact path, but the layout-chain case is still broken.Background
#917 added route-scoped implicit tags as soft tags on App Router fetch reads. The generated entry calls:
The second argument is the layout-segment list, hardcoded to
[]at every call site. As a result, the only soft tag a render carries is the page-level path tag (_N_T_/parent/child).Next.js derives a chain of layout tags in addition to the page tag — see
getImplicitTags. For/parent/child, Next.js attaches roughly:_N_T_/layout(root)_N_T_/parent/layout_N_T_/parent/child/layout(if present)_N_T_/parent/child(page)revalidatePath('/parent')writes the_N_T_/parentmarker, which matches the layout tag carried by every child render — that's what makes parent-path revalidation propagate.Repro
/parent/page.tsxand/parent/child/page.tsx, both callingfetch(url, { next: { revalidate: 3600 } })(no explicittags)./parent/childto populate the fetch cache.revalidatePath('/parent')from a server action or route handler./parent/child.Expected: the fetch in
/parent/childre-issues to the origin.Actual: the cached fetch result is reused.
The exact-path case (
revalidatePath('/parent/child')then re-rendering/parent/child) works correctly post-#917.Why this matters
Users who test path revalidation at the page level will see it work and assume the nested case works too. The failure mode is silent stale data — exactly the bug
revalidatePath()is supposed to prevent.Status
Not a regression — the nested case was also broken before #917 (where
revalidatePath()invalidated zero soft-tagged fetches). #917 is a strict improvement; this issue tracks the remaining gap.Proposed fix sketch
__pageCacheTags.cleanPathnameand inject it intosetCurrentFetchSoftTagsalongside the page tag.Either way, the fix is contained to the App Router entry template +
__pageCacheTagscallers; the cache-handler side already treats soft tags as read-time misses without persisting them on entries.References
getImplicitTagspatch-fetchpassessoftTags: implicitTags?.tags