Skip to content

Require explicit cacheLife on outer "use cache" when nesting short-lived caches#89481

Merged
unstubbable merged 13 commits intocanaryfrom
hl/nar-761
Feb 5, 2026
Merged

Require explicit cacheLife on outer "use cache" when nesting short-lived caches#89481
unstubbable merged 13 commits intocanaryfrom
hl/nar-761

Conversation

@unstubbable
Copy link
Contributor

@unstubbable unstubbable commented Feb 4, 2026

This PR adds error handling for nested "use cache" where the inner cache has a very short lifetime (zero revalidate or expire under 5 minutes) but the outer cache doesn't have an explicit cacheLife() call.

Short-lived caches become "dynamic holes" that are excluded from prerenders. When such a cache is nested inside another "use cache" without an explicit cacheLife, the outer cache's lifetime would silently become short too via propagation, which can lead to unexpected behavior. To prevent this accidental misconfiguration, Next.js now throws an error during prerendering, requiring developers to explicitly declare their intent by adding cacheLife() to the outer cache.

The implementation tracks whether revalidate and expire were explicitly set via hasExplicitRevalidate and hasExplicitExpire flags on the collected cache result. Errors are wrapped with wrapAsInvalidDynamicUsageError to capture proper stack traces and prevent userland try/catch from suppressing the build error.

Documentation has been updated with a new "Prerendering behavior" section in the cacheLife API reference explaining how short-lived caches become dynamic holes, and a "Nested short-lived caches" subsection with examples showing how to fix the error.

closes NAR-761

@nextjs-bot nextjs-bot added created-by: Next.js team PRs by the Next.js team. Documentation Related to Next.js' official documentation. tests type: next labels Feb 4, 2026
The error message collection needs to be fixed to consider consts.
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Feb 4, 2026

Tests Passed

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Feb 4, 2026

Stats from current PR

🔴 1 regression

Metric Canary PR Change Trend
node_modules Size 465 MB 465 MB 🔴 +66.6 kB (+0%) ▁▁▁██
📊 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) 455ms 455ms ▁▁▄█▁
Cold (Ready in log) 434ms 435ms ▁▅▄█▄
Cold (First Request) 1.186s 1.158s ▂█▃▆▇
Warm (Listen) 456ms 456ms ▁▁▅█▁
Warm (Ready in log) 439ms 438ms ▁▁▄█▁
Warm (First Request) 336ms 334ms ▅▃▃█▄
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 456ms 456ms ██▁▁▁
Cold (Ready in log) 441ms 439ms █▆▄▅▅
Cold (First Request) 1.878s 1.860s █▅▂▄▃
Warm (Listen) 456ms 456ms ██▁▁▁
Warm (Ready in log) 440ms 439ms █▆▄▄▅
Warm (First Request) 1.874s 1.877s █▆▂▄▃

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.000s 3.940s ▃▁▅█▁
Cached Build 3.935s 3.876s ▄▁▅█▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 13.717s 13.789s █▆▁▅▁
Cached Build 13.843s 13.831s █▆▁▅▁
node_modules Size 465 MB 465 MB 🔴 +66.6 kB (+0%) ▁▁▁██
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles: **437 kB** → **437 kB** ✅ -2 B

81 files with content-based hashes (individual files not comparable between builds)

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 764 B 762 B
Total 764 B 762 B ✅ -2 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 452 B 451 B
Total 452 B 451 B ✅ -1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.47 kB N/A -
6280-HASH.js gzip 56.9 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.53 kB N/A -
e8aec2e4-HASH.js gzip 62.5 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 256 B 254 B
main-HASH.js gzip 39.1 kB 39.1 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.52 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.48 kB -
6948ada0-HASH.js gzip N/A 62.5 kB -
9544-HASH.js gzip N/A 57.5 kB -
Total 230 kB 231 kB ⚠️ +606 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 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.49 kB 2.49 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.97 kB ✅ -1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 248 kB 249 kB
Total 374 kB 375 kB ⚠️ +878 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 617 B 614 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 32.9 kB 33.1 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 34.5 kB 34.8 kB ⚠️ +213 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 732 B 736 B
Total 732 B 736 B ⚠️ +4 B
Build Cache
Canary PR Change
0.pack gzip 3.83 MB 3.83 MB 🔴 +7.46 kB (+0%)
index.pack gzip 101 kB 104 kB 🔴 +2.59 kB (+3%)
index.pack.old gzip 102 kB 102 kB
Total 4.03 MB 4.04 MB ⚠️ +9.82 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 313 kB 313 kB
app-page-exp..prod.js gzip 167 kB 167 kB
app-page-tur...dev.js gzip 313 kB 313 kB
app-page-tur..prod.js gzip 167 kB 167 kB
app-page-tur...dev.js gzip 309 kB 309 kB
app-page-tur..prod.js gzip 165 kB 165 kB
app-page.run...dev.js gzip 309 kB 309 kB
app-page.run..prod.js gzip 165 kB 165 kB
app-route-ex...dev.js gzip 70.4 kB 70.4 kB
app-route-ex..prod.js gzip 48.9 kB 48.9 kB
app-route-tu...dev.js gzip 70.5 kB 70.5 kB
app-route-tu..prod.js gzip 49 kB 49 kB
app-route-tu...dev.js gzip 70.1 kB 70.1 kB
app-route-tu..prod.js gzip 48.7 kB 48.7 kB
app-route.ru...dev.js gzip 70 kB 70 kB
app-route.ru..prod.js gzip 48.7 kB 48.7 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 43.2 kB 43.2 kB
pages-api-tu..prod.js gzip 32.9 kB 32.9 kB
pages-api.ru...dev.js gzip 43.1 kB 43.1 kB
pages-api.ru..prod.js gzip 32.8 kB 32.8 kB
pages-turbo....dev.js gzip 52.5 kB 52.5 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 52.4 kB 52.4 kB
pages.runtim..prod.js gzip 39.3 kB 39.3 kB
server.runti..prod.js gzip 62.7 kB 62.7 kB
Total 2.78 MB 2.78 MB ⚠️ +259 B
📝 Changed Files (8 files)

Files with changes:

  • app-page-exp..ntime.dev.js
  • app-page-exp..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page-tur..ntime.dev.js
  • app-page-tur..time.prod.js
  • app-page.runtime.dev.js
  • app-page.runtime.prod.js
View diffs
app-page-exp..ntime.dev.js

Diff too large to display

app-page-exp..time.prod.js
failed to diff
app-page-tur..ntime.dev.js

Diff too large to display

app-page-tur..time.prod.js

Diff too large to display

app-page-tur..ntime.dev.js

Diff too large to display

app-page-tur..time.prod.js

Diff too large to display

app-page.runtime.dev.js

Diff too large to display

app-page.runtime.prod.js

Diff too large to display

@unstubbable unstubbable marked this pull request as ready for review February 4, 2026 18:57
@unstubbable unstubbable requested a review from icyJoseph February 5, 2026 10:43
Co-authored-by: Joseph <joseph.chamochumbi@vercel.com>
@unstubbable unstubbable enabled auto-merge (squash) February 5, 2026 15:36
@unstubbable unstubbable merged commit 469e513 into canary Feb 5, 2026
289 of 291 checks passed
@unstubbable unstubbable deleted the hl/nar-761 branch February 5, 2026 15:39
bgub pushed a commit to bgub/next.js that referenced this pull request Feb 5, 2026
…t-lived caches (vercel#89481)

This PR adds error handling for nested `"use cache"` where the inner
cache has a very short lifetime (zero `revalidate` or `expire` under 5
minutes) but the outer cache doesn't have an explicit `cacheLife()`
call.

Short-lived caches become "dynamic holes" that are excluded from
prerenders. When such a cache is nested inside another `"use cache"`
without an explicit `cacheLife`, the outer cache's lifetime would
silently become short too via
[propagation](https://nextjs.org/docs/app/api-reference/functions/cacheLife#nested-caching-behavior),
which can lead to unexpected behavior. To prevent this accidental
misconfiguration, Next.js now throws an error during prerendering,
requiring developers to explicitly declare their intent by adding
`cacheLife()` to the outer cache.

The implementation tracks whether `revalidate` and `expire` were
explicitly set via `hasExplicitRevalidate` and `hasExplicitExpire` flags
on the collected cache result. Errors are wrapped with
`wrapAsInvalidDynamicUsageError` to capture proper stack traces and
prevent userland try/catch from suppressing the build error.

Documentation has been updated with a new "Prerendering behavior"
section in the `cacheLife` API reference explaining how short-lived
caches become dynamic holes, and a "Nested short-lived caches"
subsection with examples showing how to fix the error.

closes NAR-761
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Next.js team PRs by the Next.js team. Documentation Related to Next.js' official documentation. tests type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants