Skip to content

revalidatePath() does not invalidate fetches in nested routes (missing layout implicit tags) #921

@james-elicx

Description

@james-elicx

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

  1. App with /parent/page.tsx and /parent/child/page.tsx, both calling fetch(url, { next: { revalidate: 3600 } }) (no explicit tags).
  2. Render /parent/child to populate the fetch cache.
  3. Trigger revalidatePath('/parent') from a server action or route handler.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions