fix(app-router): tag cached pages by route pattern#923
fix(app-router): tag cached pages by route pattern#923NathanDrake2406 wants to merge 1 commit intocloudflare:mainfrom
Conversation
commit: |
cf005ab to
8fd9314
Compare
There was a problem hiding this comment.
Pull request overview
Updates vinext’s App Router cache tag generation so cached fetch/page/route-handler entries are tagged using the matched route pattern (including layout chain) in addition to exact resolved pathname tags, enabling revalidatePath() to propagate through nested/dynamic routes as intended.
Changes:
- Introduces
buildPageCacheTags()helper to centralize implicit tag construction (pattern-derived layout/page/route leaves + exact pathname tags). - Wires generated App Router RSC entry code to pass
route.routeSegmentsand leaf kind intobuildPageCacheTags()for soft tags and ISR writes. - Adds/updates tests and snapshots to validate the new implicit tag behavior; adjusts knip config for the local-only vitest reporter dependency.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/page-cache-tags.test.ts | Adds focused unit coverage for pattern-derived implicit tags (dynamic segments, route groups, route handlers). |
| tests/isr-cache.test.ts | Replaces local implicit-tag derivation with buildPageCacheTags() in revalidatePath-related tests. |
| tests/app-router.test.ts | Updates codegen assertions to expect buildPageCacheTags() usage in generated RSC entry code. |
| tests/snapshots/entry-templates.test.ts.snap | Updates snapshots to reflect removal of inline __pageCacheTags and new import/usage of buildPageCacheTags(). |
| packages/vinext/src/server/implicit-tags.ts | Adds centralized implicit tag generation helper used by App Router caching. |
| packages/vinext/src/entries/app-rsc-entry.ts | Imports and uses buildPageCacheTags() when setting soft tags and when writing ISR entries. |
| knip.ts | Makes the agent dependency ignored only outside CI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8fd9314 to
9efe607
Compare
|
/bigbonk review this PR. Focus on blocking correctness issues and test gaps. |
|
/bigbonk review this PR. Focus on blocking correctness issues and whether the tests cover the real behavior and meaningful edge cases. |
|
User NathanDrake2406 does not have write permissions |
|
@NathanDrake2406 Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
1 similar comment
|
@NathanDrake2406 Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
|
@james-elicx Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
|
/bigbonk review |
|
UnknownError: ProviderInitError |
|
/bigbonk review |
There was a problem hiding this comment.
Good extraction. Moving __pageCacheTags out of the generated entry template into a typed, testable module is the right direction per the repo guidelines on keeping entries thin. The core tag generation logic is correct and the test coverage for the helper itself is solid.
I have two concerns — one blocking, one non-blocking — detailed in inline comments below.
|
Review posted. Here's a summary of what I flagged: Blocking issue: The ISR cache tests ( Non-blocking: The |
9efe607 to
576d89d
Compare
|
/bigbonk review |
There was a problem hiding this comment.
Review
Good extraction. Moving the inline __pageCacheTags out of the generated entry template into a testable implicit-tags.ts module is the right call — it follows the repo guideline of keeping entries thin and pushing real behavior into typed helpers.
The core tag generation logic is correct. I traced through appendDerivedTags for several inputs (dynamic segments, root pages, route handlers, literal page/route path segments) and the leaf detection via position-based indexing is sound. The ISR integration test now covers the key dynamic-route semantic change (pattern-based typed invalidation vs exact pathname no-type invalidation).
Two non-blocking issues below.
|
Review posted. Here's a summary of what I flagged: No blocking issues. The core logic is correct — I traced Two non-blocking suggestions:
I also noticed a stale comment in |
Cached App Router pages only carried implicit revalidatePath tags derived from the resolved URL. That left dynamic routes tagged with concrete values, so pattern-scoped invalidation like revalidatePath("/blog/[slug]", "layout") could miss entries rendered at /blog/hello.
The generated RSC entry now delegates implicit tag construction to a typed helper that combines exact pathname tags with route-segment-derived layout, page, and route handler tags. Focused tests cover dynamic route patterns, route groups, root aliases, and route handler leaves.
Refs cloudflare#921
576d89d to
3ad9bad
Compare
|
/bigbonk review |
There was a problem hiding this comment.
Good change. Extracting __pageCacheTags from the generated entry template into a typed, testable implicit-tags.ts module is the right direction per repo guidelines. The core logic is correct — I traced appendDerivedTags through dynamic segments, root pages, route groups, catch-alls, and route handlers. Position-based leaf detection is sound and avoids the original Copilot-flagged issue with literal page/route path segments.
CI is all green. Test coverage is solid: the new page-cache-tags.test.ts covers the helper thoroughly, and the ISR integration test now covers the key semantic change (pattern-based typed invalidation vs exact pathname no-type invalidation for dynamic routes).
Two minor non-blocking observations inline.
|
|
||
| const isLeaf = i - 1 === leafIndex; | ||
| if (!isLeaf) { | ||
| currentPathname = `${currentPathname}${currentPathname.endsWith("/") ? "" : "/"}layout`; |
There was a problem hiding this comment.
Non-blocking: The currentPathname.endsWith("/") guard is unreachable. Since routePath always starts with / (checked at line 29) and segments come from splitting by /, routeParts.slice(0, i).join("/") for i >= 2 will always produce paths like /blog or /blog/[slug] — never ending in /. The guard is harmless but could be replaced with a comment noting it's defensive, or just dropped.
There was a problem hiding this comment.
hmm, yeah, reading the logic, i dont think it's possible to end with / inside this loop because of the leaf check
| basePath: __basePath, | ||
| buildPageCacheTags: __pageCacheTags, | ||
| buildPageCacheTags: function(pathname, extraTags) { |
There was a problem hiding this comment.
Non-blocking (readability): This closure-wrapping pattern appears identically in two places (the cached route handler path and the uncached route handler path). Consider extracting a named helper inside _handleRequest to avoid the duplication, e.g.:
| basePath: __basePath, | |
| buildPageCacheTags: __pageCacheTags, | |
| buildPageCacheTags: function(pathname, extraTags) { | |
| buildPageCacheTags: (pathname, extraTags) => | |
| buildPageCacheTags(pathname, extraTags, route.routeSegments, "route"), |
Also, arrow function form is shorter and equivalent here since this isn't used. Same applies to the other identical instance at line ~2211.
|
Review submitted. Here's a summary: Approved with two non-blocking observations:
The core logic is correct — I traced |
What this changes
App Router page and route handler cache entries now carry implicit tags derived from the matched route pattern in addition to exact resolved pathname tags.
Why
revalidatePath(path, type)targets_N_T_tags based on route files. Entries rendered at dynamic URLs need tags such as_N_T_/blog/[slug]/layoutso layout-scoped invalidation reaches cached fetches beneath dynamic routes.Refs #921
Approach
Move implicit tag construction into
server/implicit-tags.tsand wire generated RSC entries to passroute.routeSegmentsplus the leaf kind. The helper preserves exact pathname tags, root/index aliases, route groups, dynamic segments, page leaves, and route handler leaves.This is scoped to App Router cache tag generation and does not change ISR storage semantics.
Validation
vp test run tests/page-cache-tags.test.ts tests/isr-cache.test.ts tests/fetch-cache.test.ts -t "App Router page cache tags|revalidatePath|soft tags"vp test run tests/app-router.test.ts -t "generated code threads collected fetch tags into page ISR writes|generated code stores rscData in the ISR cache entry"vp test run tests/entry-templates.test.tsvp checkvp run vinext#build