cloudflare / cloudflare/vinext

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

Open
#921 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
8.8k
Forks
406
Avg merge
2d 6h
Merged PRs (30d)
120

Description

## 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:

```ts
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`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/implicit-tags.ts). 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

- #917 (this PR introduced soft-tag plumbing; gap acknowledged in its risks/follow-ups)
- Next.js: [`getImplicitTags`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/implicit-tags.ts)
- Next.js: [`patch-fetch` passes `softTags: implicitTags?.tags`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/patch-fetch.ts)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.