Skip to content

docs(skills): preserve prefetched UI during Partial Prefetching adoption - #97712

Merged
aurorascharff merged 40 commits into
canaryfrom
codex/partial-prefetch-preservation-guide
Aug 27, 2026
Merged

aurorascharff merged 40 commits into
canaryfrom
codex/partial-prefetch-preservation-guide

Conversation

@aurorascharff

@aurorascharff aurorascharff commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

What

  • Documents how legacy prefetch={true} behavior changes with Partial Prefetching and how to choose the UI worth preserving.
  • Updates the adoption skill to capture a passing flag-off instant() baseline, use the same assertions to drive the migration, and retain them as regression coverage.
  • Adds a reusable production test rig, keeps additional per-link optimization separate from adoption, and surfaces prefetch={false} links that may no longer be needed.
  • Applies the shared test-rig fixes discovered during dogfooding to the Cache Components optimizer.

Why

Legacy prefetch={true} could include uncached dynamic content. With Partial Prefetching, it includes the shared App Shell and cached URL-specific content instead. A passing build or clean insight sweep does not prove that the intended prefetched UI survived the migration, so the skill now records that contract before adoption and verifies it unchanged afterward. The guide presents the same test workflow as an optional programmatic check for manual adopters.

Testing

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: 36bb6bf

@github-actions

github-actions Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 1.067s 1.065s ✓ ▂▂▂██
Cold (Ready in log) 1.059s 1.060s ✓ ▁▁▁██
Cold (First Request) 1.676s 1.670s ✓ ▁▁▁██
Warm (Listen) 1.065s 1.064s ✓ ▁▂▂██
Warm (Ready in log) 1.062s 1.052s ✓ ▁▁▁██
Warm (First Request) 1.670s 1.667s ✓ ▁▁▁██
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 1.066s 1.066s ✓ ▁▁▁█▁
Cold (Ready in log) 1.051s 1.060s ✓ ▁▂▁█▁
Cold (First Request) 4.698s 4.735s ✓ ▁▂▁█▁
Warm (Listen) 1.065s 1.065s ✓ ▁▁▁▇▁
Warm (Ready in log) 1.052s 1.059s ✓ ▁▂▁█▁
Warm (First Request) 4.718s 4.694s ✓ ▁▂▁█▁

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 7.471s 7.472s ✓ ▁▁▂██
Cached Build 3.921s 3.992s ✓ ▁▁▁██
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 36.727s 36.713s ✓ ▁▂▁█▁
Cached Build 36.522s 36.686s ✓ ▁▂▁█▁
node_modules Size 558 MB 558 MB ✓ █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
017_4moyv5m34.js gzip 117 B 117 B ✓
05jocc85-b73_.js gzip 341 B N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB ✓
0e5lgz-xcf6ln.js gzip 273 B 273 B ✓
0enhddgqs2dl-.js gzip 265 B 265 B ✓
0h34mejwa_7pd.js gzip 354 B 354 B ✓
0w853zxni3ro0.js gzip 1.36 kB N/A -
13lq2aw3_0bgw.js gzip 3.52 kB N/A -
19ll0d77yn0fq.js gzip 71.5 kB N/A -
1drifx6-oqf3q.js gzip 5.7 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B ✓
1m5vdnlf0deiv.js gzip 411 B 411 B ✓
1nigpy_mr0xvj.js gzip 339 B N/A -
1p0l8mn2wz5f2.js gzip 1.97 kB N/A -
1ph_nbsm967ot.js gzip 152 B N/A -
1sqoyy39jpgkq.js gzip 121 B 121 B ✓
1tf1phijqlx9j.js gzip 220 B 220 B ✓
2-54v5vj7orwp.js gzip 46.8 kB N/A -
2b05_424u1zq-.js gzip 120 B N/A -
2ca40xittx95r.js gzip 8.75 kB N/A -
2kci0vnt-49o-.js gzip 113 B 113 B ✓
2rtriq9lc6zj-.js gzip 116 B N/A -
2sxgl_rcrrn_p.js gzip 399 B N/A -
2vt62i17bhbdu.js gzip 2.35 kB N/A -
33ykhou7tl-4h.js gzip 7.53 kB N/A -
342di-crrg9kq.js gzip 119 B N/A -
36hfc2_979aw2.js gzip 119 B N/A -
38wfp6jjrrdfm.js gzip 131 B 131 B ✓
3dl1ilk37-30g.js gzip 117 B N/A -
3h0us7yy7bd_o.js gzip 13.1 kB N/A -
3kvxagoobts7h.js gzip 117 B N/A -
3p99hd9vwe6ae.js gzip 13 kB N/A -
3r0475panhk6m.js gzip 123 B N/A -
3s0i-weykgzrt.js gzip 125 B N/A -
3vjosdbddykpw.js gzip 5.88 kB N/A -
3wijk2ywqgsv9.js gzip 2.52 kB N/A -
3xuguv9lsoqw7.js gzip 65.5 kB N/A -
41u5s3oe2-erp.js gzip 2.29 kB N/A -
turbopack-0d..9ejy.js gzip 3.74 kB 3.74 kB ✓
02zc7sqlpqxn3.js gzip N/A 119 B -
06pzxqp5qiozm.js gzip N/A 5.7 kB -
0dw-iyxdctoxo.js gzip N/A 65.5 kB -
0e28eh3gbwmp0.js gzip N/A 3.51 kB -
0e5ldm8_fmypq.js gzip N/A 71.5 kB -
0jwho9fkrb_t3.js gzip N/A 2.29 kB -
0longconn5mlm.js gzip N/A 123 B -
0p4l3ozbez2cz.js gzip N/A 2.35 kB -
0sbh_15ejop4o.js gzip N/A 13 kB -
0skdm-9rpid8g.js gzip N/A 117 B -
0wv-h56sz83wn.js gzip N/A 120 B -
137xup2t69ovc.js gzip N/A 1.97 kB -
1ezuqvubf86h1.js gzip N/A 5.88 kB -
1fk10s3m1oecx.js gzip N/A 400 B -
1hut5u-lz2yif.js gzip N/A 117 B -
20qhkyyj2_tla.js gzip N/A 116 B -
2imyufe18bsb7.js gzip N/A 1.36 kB -
2p4n3-8-xixh_.js gzip N/A 341 B -
2vqdfieehz5do.js gzip N/A 8.75 kB -
2xn85ykzw2v1e.js gzip N/A 13.1 kB -
34knadd6wu9qg.js gzip N/A 119 B -
35eub0y5wk0-c.js gzip N/A 7.53 kB -
37b0_t16yn97f.js gzip N/A 153 B -
39jq2b6t6z6up.js gzip N/A 339 B -
3a9a9tvkag_-j.js gzip N/A 2.52 kB -
3d61tgick8g6b.js gzip N/A 46.8 kB -
3t121tj-h0kbm.js gzip N/A 125 B -
Total 299 kB 299 kB ⚠️ +3 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 1.02 kB 1.03 kB ✓
Total 1.02 kB 1.03 kB ⚠️ +2 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 846 B 847 B ✓
Total 846 B 847 B ⚠️ +1 B
Build Cache
Canary PR Change
00000001.sst gzip 14.1 MB 15.2 MB 🔴 +1.11 MB (+8%)
00000002.sst gzip 15 MB 12.2 MB 🟢 2.8 MB (-19%)
00000003.sst gzip 13.1 MB 14.8 MB 🔴 +1.68 MB (+13%)
00000004.sst gzip 12.8 MB 12.8 MB ✓
00000005.sst gzip 10.1 MB 10.1 MB 🔴 +27.1 kB (+0%)
00000006.sst gzip 2.75 MB 2.75 MB ✓
00000007.sst gzip 59 B 59 B ✓
00000008.meta gzip 89 B 89 B ✓
00000009.meta gzip 297 kB 297 kB ✓
00000010.meta gzip 297 kB 297 kB ✓
00000011.meta gzip 297 kB 297 kB ✓
00000012.sst gzip 52 kB 53.1 kB 🔴 +1.15 kB (+2%)
00000013.sst gzip 1.74 MB 1.73 MB 🟢 6.97 kB (0%)
00000014.sst gzip 59 B 59 B ✓
00000015.meta gzip 116 B 116 B ✓
00000016.meta gzip 327 kB 327 kB ✓
00000017.meta gzip 403 kB 403 kB ✓
00000018.sst gzip 52.6 kB 53.7 kB 🔴 +1.13 kB (+2%)
00000019.sst gzip 1.36 MB 1.37 MB 🔴 +9.72 kB (+1%)
00000020.sst gzip 59 B 59 B ✓
00000021.meta gzip 116 B 116 B ✓
00000022.meta gzip 327 kB 327 kB ✓
00000023.meta gzip 372 kB 372 kB ✓
00000024.sst gzip 52.6 kB 53.8 kB 🔴 +1.21 kB (+2%)
00000025.sst gzip 1.36 MB 1.36 MB 🔴 +7.07 kB (+1%)
00000026.sst gzip 59 B 59 B ✓
00000027.meta gzip 116 B 116 B ✓
00000028.meta gzip 327 kB 327 kB ✓
00000029.meta gzip 372 kB 372 kB ✓
00000030.sst gzip 52.1 kB 1.36 MB 🔴 +1.31 MB (+2515%)
00000031.sst gzip 1.36 MB 53.3 kB 🟢 1.3 MB (-96%)
00000032.sst gzip 59 B 59 B ✓
00000033.meta gzip 116 B 116 B ✓
00000034.meta gzip 327 kB 327 kB ✓
00000035.meta gzip 372 kB 372 kB ✓
00000036.sst gzip 52.7 kB 1.36 MB 🔴 +1.31 MB (+2487%)
00000037.sst gzip 1.36 MB 53.7 kB 🟢 1.3 MB (-96%)
00000038.sst gzip 59 B 59 B ✓
00000039.meta gzip 116 B 116 B ✓
00000040.meta gzip 327 kB 327 kB ✓
00000041.meta gzip 372 kB 372 kB ✓
CURRENT gzip 93 B 94 B 🔴 +1 B (+1%)
LOG gzip 680 B 676 B ✓
Total 79.5 MB 79.6 MB ⚠️ +51.3 kB

📦 Webpack

Client

Main Bundles
Canary PR Change
3322-HASH.js gzip 65.7 kB N/A -
4191.HASH.js gzip 169 B N/A -
9784-HASH.js gzip 5.6 kB N/A -
b1ad9f4c-HASH.js gzip 63.5 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB ✓
main-app-HASH.js gzip 253 B 253 B ✓
main-HASH.js gzip 39.8 kB 39.7 kB ✓
webpack-HASH.js gzip 1.68 kB 1.68 kB ✓
3577.HASH.js gzip N/A 168 B -
578-HASH.js gzip N/A 65.8 kB -
8590-HASH.js gzip N/A 5.57 kB -
a8984546-HASH.js gzip N/A 63.5 kB -
Total 236 kB 237 kB ⚠️ +106 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB ✓
Total 39.4 kB 39.4 kB ✓
Pages
Canary PR Change
_app-HASH.js gzip 194 B 193 B ✓
_error-HASH.js gzip 181 B 182 B ✓
css-HASH.js gzip 334 B 331 B ✓
dynamic-HASH.js gzip 1.81 kB 1.81 kB ✓
edge-ssr-HASH.js gzip 255 B 253 B ✓
head-HASH.js gzip 349 B 351 B ✓
hooks-HASH.js gzip 382 B 384 B ✓
image-HASH.js gzip 5.05 kB 5.05 kB ✓
index-HASH.js gzip 260 B 259 B ✓
link-HASH.js gzip 2.48 kB 2.48 kB ✓
routerDirect..HASH.js gzip 317 B 318 B ✓
script-HASH.js gzip 384 B 386 B ✓
withRouter-HASH.js gzip 316 B 315 B ✓
1afbb74e6ecf..834.css gzip 106 B 106 B ✓
Total 12.4 kB 12.4 kB ✅ -6 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 128 kB 128 kB ✓
page.js gzip 293 kB 293 kB ✓
Total 421 kB 421 kB ✅ -420 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 602 B 601 B ✓
middleware-r..fest.js gzip 156 B 156 B ✓
middleware.js gzip 45.8 kB 45.6 kB ✓
edge-runtime..pack.js gzip 842 B 842 B ✓
Total 47.4 kB 47.2 kB ✅ -211 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 699 B 699 B ✓
Total 699 B 699 B ✓
Build Cache
Canary PR Change
0.pack gzip 4.8 MB 4.8 MB 🟢 8.24 kB (0%)
index.pack gzip 124 kB 125 kB ✓
index.pack.old gzip 125 kB 125 kB ✓
Total 5.05 MB 5.04 MB ✅ -7.67 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 377 kB 377 kB ✓
app-page-exp..prod.js gzip 207 kB 207 kB ✓
app-page-tur...dev.js gzip 377 kB 377 kB ✓
app-page-tur..prod.js gzip 207 kB 207 kB ✓
app-page-tur...dev.js gzip 373 kB 373 kB ✓
app-page-tur..prod.js gzip 205 kB 205 kB ✓
app-page.run...dev.js gzip 373 kB 373 kB ✓
app-page.run..prod.js gzip 205 kB 205 kB ✓
app-route-ex...dev.js gzip 82.9 kB 82.9 kB ✓
app-route-ex..prod.js gzip 55.8 kB 55.8 kB ✓
app-route-tu...dev.js gzip 82.9 kB 82.9 kB ✓
app-route-tu..prod.js gzip 55.9 kB 55.9 kB ✓
app-route-tu...dev.js gzip 82.5 kB 82.5 kB ✓
app-route-tu..prod.js gzip 55.6 kB 55.6 kB ✓
app-route.ru...dev.js gzip 82.4 kB 82.4 kB ✓
app-route.ru..prod.js gzip 55.6 kB 55.6 kB ✓
dev-validati...dev.js gzip 139 kB 139 kB ✓
dev-validati...dev.js gzip 139 kB 139 kB ✓
dev-validati...dev.js gzip 137 kB 137 kB ✓
dev-validati...dev.js gzip 137 kB 137 kB ✓
dist_client_...dev.js gzip 324 B 324 B ✓
dist_client_...dev.js gzip 326 B 326 B ✓
dist_client_...dev.js gzip 318 B 318 B ✓
dist_client_...dev.js gzip 317 B 317 B ✓
pages-api-tu...dev.js gzip 46.3 kB 46.3 kB ✓
pages-api-tu..prod.js gzip 34.3 kB 34.3 kB ✓
pages-api.ru...dev.js gzip 46.2 kB 46.2 kB ✓
pages-api.ru..prod.js gzip 34.3 kB 34.3 kB ✓
pages-turbo....dev.js gzip 54.7 kB 54.7 kB ✓
pages-turbo...prod.js gzip 39.7 kB 39.7 kB ✓
pages.runtim...dev.js gzip 54.7 kB 54.7 kB ✓
pages.runtim..prod.js gzip 39.6 kB 39.6 kB ✓
server.runti..prod.js gzip 66 kB 66 kB ✓
use-cache-pr...dev.js gzip 73.4 kB 73.4 kB ✓
use-cache-pr...dev.js gzip 73.4 kB 73.4 kB ✓
use-cache-pr...dev.js gzip 71.7 kB 71.7 kB ✓
use-cache-pr...dev.js gzip 71.6 kB 71.6 kB ✓
Total 4.14 MB 4.14 MB ✓
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/a907b8b75b6509f350da48815cefdd41605b5154/next

Commit: a907b8b

@aurorascharff
aurorascharff force-pushed the codex/partial-prefetch-preservation-guide branch from 6b78989 to 5a115ac Compare August 21, 2026 20:27
Comment thread skills/next-partial-prefetching-adoption/SKILL.md Outdated
@aurorascharff
aurorascharff force-pushed the codex/partial-prefetch-preservation-guide branch 2 times, most recently from 6866e13 to fc3c6d6 Compare August 21, 2026 20:33
@aurorascharff
aurorascharff force-pushed the codex/partial-prefetch-preservation-guide branch from fc3c6d6 to db4ca3a Compare August 21, 2026 20:36
dianatofficial

This comment was marked as low quality.

@aurorascharff
aurorascharff marked this pull request as ready for review August 22, 2026 10:09
Comment thread docs/01-app/02-guides/adopting-partial-prefetching.mdx Outdated
Comment thread docs/01-app/02-guides/adopting-partial-prefetching.mdx Outdated
Comment thread docs/01-app/02-guides/adopting-partial-prefetching.mdx Outdated
@aurorascharff aurorascharff changed the title docs: verify legacy prefetch behavior during adoption docs: preserve prefetched UI during Partial Prefetching adoption Aug 27, 2026
@aurorascharff aurorascharff changed the title docs: preserve prefetched UI during Partial Prefetching adoption skill: preserve prefetched UI during Partial Prefetching adoption Aug 27, 2026
@aurorascharff aurorascharff changed the title skill: preserve prefetched UI during Partial Prefetching adoption docs(skills): preserve prefetched UI during Partial Prefetching adoption Aug 27, 2026
| [Reads URL data, or has cached content that depends on it](#url-data) | Keep `prefetch={true}` to resolve the content ahead of the click. |
| [Delivers real-time content that must stay fresh per request](#real-time-content) | Remove `prefetch={true}` and let the content stream in. |

### Verify prefetched UI with tests

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a nit but the table above is a summary of the sections that follow — should it be placed at the end of this run

After, Real-time content, and before Adopting incrementally

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah true!


For larger migrations, you can [adopt incrementally](#adopting-incrementally) with the global flag off. The [dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial) insight identifies each destination that still uses the legacy full prefetch.

Both insights are development-only and never block the build. They appear in the dev overlay with fix cards that link the docs page for each fix. If a route isn't ready to adopt, export [`instant = false`](/docs/app/api-reference/file-conventions/route-segment-config/instant) from its page or layout to opt the route out of instant-navigation validation, and return to it later.

@icyJoseph icyJoseph Aug 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item 1 used to end with

guided by the dynamic data during prefetching insight.

And item 2 still ends with:

resolving the URL data outside of Suspense insight.

And then it was an OK flow with Both insights — with this format, the list ends, and we talk about larger migrations, and an insight, and then we follow with 49, as a reader I may not connect to the insight on item 2.

Maybe a nit, but it feels a bit disconnected. I guess, item 2 is doing too much now?


## Enable Partial Prefetching

> **Good to know**: In an existing app, review the UI delivered by [`<Link prefetch={true}>`](#migrate-existing-full-prefetches) before enabling Partial Prefetching. This is the legacy full prefetch that the migration may change.

@icyJoseph icyJoseph Aug 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the legacy full prefetch that the migration may change

Should be?

Those links use the legacy full prefetch, which change their behavior when Partial Prefetching is enabled.

```

After enabling the flag, every `<Link>` prefetches its destination's App Shell, and `<Link prefetch={true}>` no longer includes the route's dynamic content. Audit those links next to preserve what they delivered. A new project has no legacy links to audit and is done here.
After enabling the flag, every `<Link>` prefetches its destination's App Shell. A link with `prefetch={true}` can also resolve cached URL-specific content, but it no longer includes uncached dynamic content from the legacy full prefetch. This migration step doesn't apply to new projects.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This migration step doesn't apply to new project

Nit, but we haven't mentioned (in prose) migration just yet. And a new project doesn't need to migrate either, just adopt.

This doesn't apply to new projects.

But it feels incomplete like that too

@aurorascharff
aurorascharff enabled auto-merge (squash) August 27, 2026 19:36
@aurorascharff
aurorascharff merged commit 24ed66a into canary Aug 27, 2026
127 checks passed
@aurorascharff
aurorascharff deleted the codex/partial-prefetch-preservation-guide branch August 27, 2026 19:46
icyJoseph pushed a commit that referenced this pull request Sep 7, 2026
…ion (#97712)

## What

- Documents how legacy `prefetch={true}` behavior changes with Partial
Prefetching and how to choose the UI worth preserving.
- Updates the adoption skill to capture a passing flag-off `instant()`
baseline, use the same assertions to drive the migration, and retain
them as regression coverage.
- Adds a reusable production test rig, keeps additional per-link
optimization separate from adoption, and surfaces `prefetch={false}`
links that may no longer be needed.
- Applies the shared test-rig fixes discovered during dogfooding to the
Cache Components optimizer.

## Why

Legacy `prefetch={true}` could include uncached dynamic content. With
Partial Prefetching, it includes the shared App Shell and cached
URL-specific content instead. A passing build or clean insight sweep
does not prove that the intended prefetched UI survived the migration,
so the skill now records that contract before adoption and verifies it
unchanged afterward. The guide presents the same test workflow as an
optional programmatic check for manual adopters.

## Testing

- Prettier, Alex, and skill frontmatter validation
- Dogfooded on v0 in vercel/v0#28842
- Partial Prefetching adoption eval: 6/6 in
#97759

(cherry picked from commit 24ed66a)
eps1lon pushed a commit that referenced this pull request Sep 7, 2026
- docs: explain origin matching for allowedOrigins and allowedDevOrigins
(#97805)
- docs: improve discovery summaries (#97982)
- docs(skills): preserve prefetched UI during Partial Prefetching
adoption (#97712)
- docs: add layout stability guidance for videos and iframes (#98070)
- docs: document preloading with Cache Components (#97864)
- docs: note that experimental.taint taints process.env (#98145)
- docs: add API reference pages for skipProxyUrlNormalize and
skipTrailingSlashRedirect (#97967)
- docs: keep generateStaticParams during Cache Components migration
(docs part of #98209)
- docs: Remove version label from Turbopack in Pages Router (#98316)

---------

Co-authored-by: Rich Haines <hello@richardhaines.dev>
Co-authored-by: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com>
aurorascharff added a commit that referenced this pull request Sep 10, 2026
## Summary

- add a Partial Prefetching optimizer Skill for selected source Links
and destinations
- keep prefetch stages and Link policy in the public [Optimizing
prefetching
guide](https://nextjs.org/docs/app/guides/optimizing-prefetching), and
rely on the same static-shell documentation as the Cache Components
optimizer for cache placement, Suspense, loading UI, and authorization
- keep the Skill focused on the product decision, production `instant()`
loop, differential verification, and mutation freshness when an
optimization adds a cache around mutable data
- reference the canonical
[`unstable_prefetch()`](https://nextjs.org/docs/app/api-reference/functions/prefetch)
and
[`unstable_navigation()`](https://nextjs.org/docs/app/api-reference/functions/navigation)
docs instead of explaining the APIs again in the Skill
- link cache-writing optimizations to the canonical
[Revalidating](https://nextjs.org/docs/app/getting-started/caching-and-revalidating#revalidating)
documentation and verify a populated-cache mutation in the Skill and
eval
- bundle a self-contained production rig aligned with the related
adoption and optimizer Skills, tuned to record each source-link and
prefetch-stage contract
- add an eval that compares baseline, the bundled guide, and the local
Skill against the same prefetched-UI and cache-freshness goal
- document the optimizer in the AI agents guide

This follows the same guide/Skill split as #97737. Partial Prefetching
adoption preserves existing behavior; this optimizer is the later
decision about what is worth preparing before a click and what should
wait for navigation.

Related: #97712, #97737, #97759, #98122, #98175

## Eval

One run per treatment with Claude Opus 4.8 and Claude Sonnet 4.5, using
a fixed Claude Haiku 4.5 judge. The bundled-guide and Skill treatments
use a local Next.js tarball containing the new guide and API references.

| Model | Treatment | Criteria | Score change | Overall | Duration |
Tokens processed | Total run cost | Cost change |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Opus 4.8 | Baseline | 10/10 (100%) | — | Pass | 18m 42s | 3.39M |
$3.46 | — |
| Opus 4.8 | Bundled guide | 9/10 (90%) | -10 pp | Fail | 13m 37s |
3.02M | $2.84 | -18.0% |
| Opus 4.8 | Local Skill | 10/10 (100%) | +10 pp | Pass | 17m 22s |
2.80M | $2.54 | -10.6% |
| Sonnet 4.5 | Baseline | 2/10 (20%) | — | Fail | 15m 55s | 1.97M |
$1.06 | — |
| Sonnet 4.5 | Bundled guide | 5/10 (50%) | +30 pp | Fail | 15m 49s |
2.17M | $1.11 | +4.5% |
| Sonnet 4.5 | Local Skill | 8/10 (80%) | +30 pp | Fail | 15m 31s |
1.41M | $0.77 | -30.4% |

Token totals count each unique model message once and include uncached
input, cache creation, cache reads, and output. Cache reads account for
most of the total in every treatment. Total run cost applies the current
AI Gateway base rates to each token category: Opus 4.8 costs $5/M input,
$6.25/M cache writes, $0.50/M cache reads, and $25/M output; Sonnet 4.5
costs $3/M input, $3.75/M cache writes, $0.30/M cache reads, and $15/M
output. The six agent runs cost $11.78 in total.

Compared with baseline, the Opus Skill run maintained a 100% criteria
pass rate while reducing cost by 26.7%. The Sonnet Skill run improved
the criteria pass rate from 20% to 80%, a 60 percentage-point increase,
while reducing cost by 27.3%.

The stronger model could solve the fixture without the Skill, although
it independently searched the bundled Next.js documentation and took
slightly longer than the Skill treatment. The Opus guide-only run
implemented the correct behavior but missed the required
RED-before-GREEN proof.

The Sonnet treatments show a clearer progression. Baseline did not
implement the required caching, navigation stage, `instant()` contract,
or cache invalidation. The bundled guide added the cached summary and
production test, but missed the navigation stage, deferred-state
assertions, tag invalidation, and RED-before-GREEN proof. The Skill
treatment completed the `instant()` RED-to-GREEN loop and final
production behavior, but left `connection()` in the related-sessions
read and used path revalidation instead of tagging the cached read. It
therefore passed 8 of 10 criteria but failed the overall eval.

All six measured runs completed through grading. Chromium and its system
dependencies are provisioned before the agent starts, so missing browser
libraries did not determine these results.

## Verification

- `pnpm --filter=next build`
- `pnpm prettier --check` for the changed guide, prompt, Skill, and eval
fixture
- `pnpm exec tsc -p
evals/evals/agent-051-optimize-partial-prefetching/tsconfig.json
--noEmit`
- `node --check run-evals.js`
- `git diff --check`
- packed a local Next.js tarball and confirmed it contains the optimizer
guide plus the `navigation()` and `prefetch()` references
- verified the `navigation()` contract in the minimal reproduction and
NextBeats on Next.js 16.4.0-canary.22
- merged the latest `canary`, preserving both generic fixture setup and
Playwright provisioning

<!-- NEXT_JS_LLM -->
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Sep 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants