next-leak 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -9
- package/THIRD-PARTY-NOTICES.md +7 -7
- package/dist/bootstrap.js +27 -1
- package/dist/{chunk-4FYSLLSX.js → chunk-4NKGNMEA.js} +19 -4
- package/dist/{chunk-QNYUT2XF.js → chunk-5VXBMWDN.js} +1828 -1140
- package/dist/{chunk-YLHE4N5G.js → chunk-7WUE3WOI.js} +9 -5
- package/dist/{chunk-XHPUAMJG.js → chunk-E464SO7G.js} +39 -2
- package/dist/{chunk-AUUMRTZZ.js → chunk-YPVBX3QA.js} +4 -3
- package/dist/cli.js +3 -3
- package/dist/confidence.d.ts +21 -0
- package/dist/control-server.d.ts +21 -0
- package/dist/{html-report-O7ILFOF5.js → html-report-6KGPHCMA.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/isr.d.ts +17 -1
- package/dist/{issue-report-FPVOZ6HU.js → issue-report-OALZZLVR.js} +2 -2
- package/dist/peak-pressure.d.ts +70 -5
- package/dist/request-probe.d.ts +18 -0
- package/dist/route-config.d.ts +23 -0
- package/dist/runner.d.ts +10 -4
- package/dist/trend.d.ts +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -29,22 +29,43 @@ next-leak found the growth, the retaining object and the chain that holds it —
|
|
|
29
29
|
without being told what to look for. Next.js 16.3.0 has since fixed it.
|
|
30
30
|
|
|
31
31
|
**Verified against real Next.js issues**, not synthetic fixtures. Issue states
|
|
32
|
-
checked 2026-09-
|
|
32
|
+
checked 2026-09-19:
|
|
33
33
|
|
|
34
34
|
| Issue | What it is | Measured | State today |
|
|
35
35
|
|---|---|---|---|
|
|
36
|
-
| [#
|
|
37
|
-
| [#
|
|
36
|
+
| [#97938](https://github.com/vercel/next.js/issues/97938) | `use cache` / `cacheComponents`: `AbortSignal.any` composites never released (canonical issue since Sep 9; #97776 and #97464 are duplicates) | +705 KB per request on 16.3.3, flat on 16.2.6 | fixed in **16.3.5** (Sep 11) by [#98448](https://github.com/vercel/next.js/pull/98448), the backport of [#97476](https://github.com/vercel/next.js/pull/97476); re-measured Sep 14: +709 KB per request on 16.3.4, flat on 16.3.5 |
|
|
37
|
+
| [#97776](https://github.com/vercel/next.js/issues/97776) | `use cache`: the same composites, reported from production | the #97938 run above | closed Sep 9 as a duplicate of #97938; fixed in 16.3.5 |
|
|
38
38
|
| [#96533](https://github.com/vercel/next.js/issues/96533) | ISR revalidation holds RSC buffers between collections | 4–5 MB of `arrayBuffers` held vs 0.32 MB retained | **open** |
|
|
39
|
-
| [#97464](https://github.com/vercel/next.js/issues/97464) | Static-gen worker retains per prerendered page | OOM after 1617 and 1525 of 2504 pages on 16.3.3; 16.2.12 finishes at 0.05 MB/page | closed Aug 31 as a duplicate
|
|
40
|
-
| [#97802](https://github.com/vercel/next.js/issues/97802) | Turbopack compilation saturates the container before rendering anything |
|
|
39
|
+
| [#97464](https://github.com/vercel/next.js/issues/97464) | Static-gen worker retains per prerendered page | OOM after 1617 and 1525 of 2504 pages on 16.3.3; 16.2.12 finishes at 0.05 MB/page | closed Aug 31 as a duplicate; same fix, in 16.3.5 — not re-measured here yet |
|
|
40
|
+
| [#97802](https://github.com/vercel/next.js/issues/97802) | Turbopack compilation saturates the container before rendering anything | not attributable to a version: see below. [#98581](https://github.com/vercel/next.js/pull/98581) (merged Sep 16, in `16.4.0-canary.34`) doesn't move it: cold build peak 5,161 MB on canary.33 and 5,279 MB on canary.35 (`next-leak build`); under 4 GB neither finishes compiling in 600 s, under 5 GB both compile in about a minute | **open** — the other fix from Vercel, [#98611](https://github.com/vercel/next.js/pull/98611), is still a draft |
|
|
41
|
+
| [#98707](https://github.com/vercel/next.js/issues/98707) | `next dev`: a route handler compiled after N pages costs ~16 MB × N | reproduced independently (10 pages, 34 handlers, Node 24.18): 851 MB on `16.3.0-canary.100`, **2,354 MB on canary.101**, 1,704 MB on 16.3.5; 1,461 MB requesting the handlers first | **open** — the reporter's bisect to `16.3.0-canary.101` holds |
|
|
41
42
|
| [#92287](https://github.com/vercel/next.js/issues/92287) | Cache Components: unbounded `arrayBuffers` under load | 37.5 MB of arrayBuffers held between collections, 37x what it retains (16.3.1) | **open** |
|
|
42
|
-
| [#84884](https://github.com/vercel/next.js/issues/84884) | axios + `AbortSignal` in middleware |
|
|
43
|
+
| [#84884](https://github.com/vercel/next.js/issues/84884) | axios + `AbortSignal` in middleware: a reference cycle through undici's `Request` finalizer, closed when Turbopack's scope hoisting inlines axios's `composeSignals` | +17.02 MB/1000 req on 16.3.5 (Node 24.18), +4.62 (Node 24.21); flat with scope hoisting off | **open** — still leaks on 16.3.5 and 16.4.0-canary.29; fix proposed in undici ([nodejs/undici#5822](https://github.com/nodejs/undici/pull/5822)); workaround: `experimental: { turbopackScopeHoisting: false }` |
|
|
43
44
|
| [#89091](https://github.com/vercel/next.js/issues/89091) | zlib retention on mid-stream aborts | +42.5 MB/1000 aborted req on 16.1.5; **+0.03 on 16.3.1** | closed |
|
|
44
45
|
| [#95094](https://github.com/vercel/next.js/issues/95094) | Middleware `setTimeout` ids retained by the sandbox | 112 MB retained; flat after the fix | fixed in 16.3.0 |
|
|
45
46
|
| [#94890](https://github.com/vercel/next.js/issues/94890) | Router LRU cache doesn't count its keys | 26.7 → 71.9 MB | fixed in 16.3.0 |
|
|
46
47
|
| [#94919](https://github.com/vercel/next.js/issues/94919) | Retention on client aborts | 39 → 139 MB · [with a caveat](#scope-and-limits-read-before-filing-issues) | fixed in 16.3.0 |
|
|
47
48
|
|
|
49
|
+
**#97802, and a measurement of ours that did not hold up.** Earlier versions of this
|
|
50
|
+
table said the regression was bisected to 16.3.0, on the strength of a 16.2.12 arm
|
|
51
|
+
that peaked around 2 GB while 16.3.x pinned the 4 GB ceiling. Re-measured on
|
|
52
|
+
2026-09-16 in a real cgroup, that comparison falls apart:
|
|
53
|
+
`turbopackRustReactCompiler` does not exist before 16.3, so 16.2.12 rejects the key
|
|
54
|
+
and silently compiles through the Babel React Compiler while every 16.3 arm uses the
|
|
55
|
+
Rust one. Hold the version fixed and change only the compiler and
|
|
56
|
+
`16.3.0-canary.100` goes from 4,096 MiB with 12.3M at-limit events to 2,250 MiB with
|
|
57
|
+
zero. Give the Babel arms 2,400 s and neither 16.2.12 (2,180 MiB) nor 16.3.5
|
|
58
|
+
(1,692 MiB) ever reaches `Compiled successfully` — the low peak was unfinished work,
|
|
59
|
+
not headroom. With the compiler equalised and 6 GB of room, 16.3.5 is the only arm
|
|
60
|
+
that finishes the build at all, and what kills 16.2.12 and `canary.0` happens after
|
|
61
|
+
compilation, in prerendering. So this row no longer claims a version: the fixture
|
|
62
|
+
does not separate 16.2 from 16.3.
|
|
63
|
+
|
|
64
|
+
Two things worth taking from this if you measure builds yourself: when every arm
|
|
65
|
+
pins at exactly the cgroup ceiling, the ceiling is the measurement and not the
|
|
66
|
+
versions, so the peak cannot be the verdict; and an arm that never reaches the end
|
|
67
|
+
of compilation is not an arm that fits, however low its peak looks.
|
|
68
|
+
|
|
48
69
|
The closed ones are kept deliberately: a tool that only lists open bugs
|
|
49
70
|
looks impressive until the bugs close, and what those rows show is that the
|
|
50
71
|
measurements matched what the fixes turned out to be. #94919 is the sharpest —
|
|
@@ -220,6 +241,7 @@ separates them, because each one has a different fix:
|
|
|
220
241
|
| A route that is expensive, not leaky | `failed` under load it cannot sustain, flat once concurrency fits | Real leaks survive forced GC at any concurrency; saturation disappears when load drops |
|
|
221
242
|
| Growth that pauses and resumes (stepwise) | `leak` | A healthy route gives back 20-30% of its growth; a stepwise leak gives back nothing |
|
|
222
243
|
| A cache filling up under the load that measures it | `saturating` | A bounded store grows by less each cycle as new keys get rarer; a leak does not decelerate |
|
|
244
|
+
| Memory a forced GC reclaims that production never reclaims in time | `pressure` | Every verdict sample is post-GC; the peaks are sampled under load, and a ceiling every settled cycle comes back to is a regime, not an episode |
|
|
223
245
|
| Native/buffer memory with a flat JS heap | `leak (external)` or an explicit RSS note | Heap, `external` and RSS are sampled and judged separately |
|
|
224
246
|
| A leak in your code vs a dependency vs Next itself | `culprit: src/app/x/page.tsx (your code)` — or the package, or framework internals | Retainer chains mapped through the build's source maps |
|
|
225
247
|
| A run whose own evidence is weak | `low confidence` warnings, or the verdict is withdrawn | Every run audits itself: did the load land, did the heap settle, does one cycle carry the average, did the heap run into its own ceiling |
|
|
@@ -256,6 +278,27 @@ separates them, because each one has a different fix:
|
|
|
256
278
|
draft is generated. When the load was driving a cache with keys it had never
|
|
257
279
|
served, the report says so on any growing route and points at `{n%N}` to
|
|
258
280
|
bound the key set — measure again that way before believing the number.
|
|
281
|
+
- **`pressure`** — nothing is retained, and the process is still heading for a
|
|
282
|
+
ceiling. Every sample a verdict is computed from is taken after a forced GC,
|
|
283
|
+
and production runs none of those, so the retention verdicts above are
|
|
284
|
+
structurally blind to memory a full collection *does* reclaim but that the
|
|
285
|
+
runtime does not reclaim fast enough on its own. This is that case: the
|
|
286
|
+
post-GC curve is flat or falling, the peak sampled under load is far above
|
|
287
|
+
what the route retains, **and** every settled cycle reaches that height. On
|
|
288
|
+
the reproduction for [#92287](https://github.com/vercel/next.js/issues/92287)
|
|
289
|
+
the app allocated about 1 MB of `arrayBuffers` per request, passed 3 GB and
|
|
290
|
+
was OOM-killed — and the old verdict was `stable`, which is precisely the trap
|
|
291
|
+
this tool exists to warn other people about. A single high peak is *not* this:
|
|
292
|
+
one cycle that reached a ceiling for a reason that did not repeat stays
|
|
293
|
+
`stable` with a peak note, because an episode is not a regime. What separates
|
|
294
|
+
this from an app entitled to a large working set is not the shape of the
|
|
295
|
+
curve, it is that a working set survives the forced collection and lands in
|
|
296
|
+
what the route retains, which acquits it on the ratio alone. These
|
|
297
|
+
routes are not measured again (the run already saw the ceiling it is
|
|
298
|
+
reporting) and get no issue draft: the finding is real, but it is not
|
|
299
|
+
retention, so there is nothing for a snapshot diff to name. The fix is
|
|
300
|
+
usually allocation rate, response size or concurrency, not a missing
|
|
301
|
+
`delete`.
|
|
259
302
|
- **`inconclusive`** — the evidence does not decide. The run does not stop there: any inconclusive route is **measured again automatically**, with twice the cycles, and the second pass is what you see (`resolved at 8 cycles` next to the verdict). On the reproduction for [#95094](https://github.com/vercel/next.js/issues/95094), `--quick` alone reports `inconclusive` on three deltas and then comes back with the leak. `--no-resolve` turns the second pass off; when even that is undecided, the re-run command is still printed.
|
|
260
303
|
- **`failed`** — the route errored under load (auth redirects, POST-only endpoints). >1% non-2xx aborts measurement instead of measuring garbage. That's by design. A process that died of **heap exhaustion** is not one of these: it reports `leak`, because a route that could not survive its own load did not fail to be measured — it was measured right up to the point where it stopped fitting. The verdict comes from that outcome, not from the shape of the truncated curve, which is the same rule `next-leak build` applies to a static-generation worker that dies. The run prints the cycles it survived and the growth up to the death, and exits 0 with a finding rather than 1 with an error.
|
|
261
304
|
|
|
@@ -282,9 +325,26 @@ That is a real measurement of the reproduction in
|
|
|
282
325
|
app on 16.3.1 under a shorter profile still reaches 544 MB against 33.8 MB
|
|
283
326
|
retained, so the shape has not gone anywhere. The note fires when the peak heap comes within
|
|
284
327
|
75% of `--max-old-space`, or when peak RSS is at least 8× the retained heap
|
|
285
|
-
and above 512 MB.
|
|
286
|
-
|
|
287
|
-
|
|
328
|
+
and above 512 MB. A peak is the highest value *sampled* (every 250 ms), so it
|
|
329
|
+
is a lower bound.
|
|
330
|
+
|
|
331
|
+
A single high peak stays a note and leaves the verdict alone: one cycle that
|
|
332
|
+
reached a ceiling may have reached it for a reason that will not happen again.
|
|
333
|
+
When **every settled cycle** comes back to that height the verdict becomes
|
|
334
|
+
[`pressure`](#reading-the-verdicts) instead, because at that point the run is no
|
|
335
|
+
longer describing an episode, it is describing what the route does under load —
|
|
336
|
+
which is the number a container is sized against. The peak is not required to
|
|
337
|
+
climb, and asking it to would make the verdict unreachable: each cycle is
|
|
338
|
+
preceded by a forced collection and runs the same traffic, so the peak converges
|
|
339
|
+
on traffic × cost-per-request rather than ramping. Measured on the #92287
|
|
340
|
+
reproduction on 2026-09-24, the rss peaks across four cycles were 1229, 1344,
|
|
341
|
+
1369 and 1379 MB — an asymptote.
|
|
342
|
+
|
|
343
|
+
What the route retains, for this ratio, is the **floor** of its post-GC cycle
|
|
344
|
+
samples rather than the last of them. Those samples follow a forced collection
|
|
345
|
+
but can still carry memory the collector had not reached yet, and on that same
|
|
346
|
+
reproduction they swung between 45 MB and 217 MB inside a single run, which by
|
|
347
|
+
itself decided whether the note appeared at all.
|
|
288
348
|
|
|
289
349
|
If the measured process dies at the limit instead of merely approaching it,
|
|
290
350
|
the route fails saying exactly that, with the limit in force and how to raise
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -56,7 +56,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
59
|
-
## @babel/generator@7.29.
|
|
59
|
+
## @babel/generator@7.29.8
|
|
60
60
|
|
|
61
61
|
License: MIT
|
|
62
62
|
https://babel.dev/docs/en/next/babel-generator
|
|
@@ -184,7 +184,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
184
184
|
|
|
185
185
|
---
|
|
186
186
|
|
|
187
|
-
## @babel/parser@7.29.
|
|
187
|
+
## @babel/parser@7.29.8
|
|
188
188
|
|
|
189
189
|
License: MIT
|
|
190
190
|
https://babel.dev/docs/en/next/babel-parser
|
|
@@ -245,7 +245,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
245
245
|
|
|
246
246
|
---
|
|
247
247
|
|
|
248
|
-
## @babel/traverse@7.29.
|
|
248
|
+
## @babel/traverse@7.29.8
|
|
249
249
|
|
|
250
250
|
License: MIT
|
|
251
251
|
https://babel.dev/docs/en/next/babel-traverse
|
|
@@ -460,7 +460,7 @@ SOFTWARE.
|
|
|
460
460
|
|
|
461
461
|
---
|
|
462
462
|
|
|
463
|
-
## @memlab/core@2.0.
|
|
463
|
+
## @memlab/core@2.0.5
|
|
464
464
|
|
|
465
465
|
License: MIT
|
|
466
466
|
https://github.com/facebook/memlab#readme
|
|
@@ -491,7 +491,7 @@ SOFTWARE.
|
|
|
491
491
|
|
|
492
492
|
---
|
|
493
493
|
|
|
494
|
-
## @memlab/e2e@2.0.
|
|
494
|
+
## @memlab/e2e@2.0.5
|
|
495
495
|
|
|
496
496
|
License: MIT
|
|
497
497
|
https://github.com/facebook/memlab#readme
|
|
@@ -522,7 +522,7 @@ SOFTWARE.
|
|
|
522
522
|
|
|
523
523
|
---
|
|
524
524
|
|
|
525
|
-
## @memlab/heap-analysis@2.0.
|
|
525
|
+
## @memlab/heap-analysis@2.0.5
|
|
526
526
|
|
|
527
527
|
License: MIT
|
|
528
528
|
https://github.com/facebook/memlab#readme
|
|
@@ -553,7 +553,7 @@ SOFTWARE.
|
|
|
553
553
|
|
|
554
554
|
---
|
|
555
555
|
|
|
556
|
-
## @memlab/lens@2.0.
|
|
556
|
+
## @memlab/lens@2.0.5
|
|
557
557
|
|
|
558
558
|
License: MIT
|
|
559
559
|
https://github.com/facebook/memlab#readme
|
package/dist/bootstrap.js
CHANGED
|
@@ -3,6 +3,8 @@ import "./chunk-6XYFBOL2.js";
|
|
|
3
3
|
|
|
4
4
|
// src/bootstrap.ts
|
|
5
5
|
import { mkdir, writeFile } from "fs/promises";
|
|
6
|
+
import http2 from "http";
|
|
7
|
+
import https from "https";
|
|
6
8
|
import path2 from "path";
|
|
7
9
|
|
|
8
10
|
// src/control-server.ts
|
|
@@ -21,6 +23,7 @@ async function forceGc(passes = 3) {
|
|
|
21
23
|
}
|
|
22
24
|
return true;
|
|
23
25
|
}
|
|
26
|
+
var requestProbe = globalThis;
|
|
24
27
|
function sampleMemory(gcExposed) {
|
|
25
28
|
const usage = process.memoryUsage();
|
|
26
29
|
return {
|
|
@@ -28,7 +31,12 @@ function sampleMemory(gcExposed) {
|
|
|
28
31
|
heapUsed: usage.heapUsed,
|
|
29
32
|
rss: usage.rss,
|
|
30
33
|
external: usage.external,
|
|
31
|
-
arrayBuffers: usage.arrayBuffers
|
|
34
|
+
arrayBuffers: usage.arrayBuffers,
|
|
35
|
+
pid: process.pid,
|
|
36
|
+
ppid: process.ppid,
|
|
37
|
+
argv: process.argv,
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
...requestProbe.__nextLeakServedRequests === void 0 ? {} : { servedRequests: requestProbe.__nextLeakServedRequests }
|
|
32
40
|
};
|
|
33
41
|
}
|
|
34
42
|
async function startControlServer(options) {
|
|
@@ -88,12 +96,30 @@ async function startControlServer(options) {
|
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
// src/request-probe.ts
|
|
100
|
+
function countServedRequests(module) {
|
|
101
|
+
const original = module.createServer;
|
|
102
|
+
module.createServer = function patched(...args) {
|
|
103
|
+
const server = original.apply(this, args);
|
|
104
|
+
server.on("request", () => {
|
|
105
|
+
requestProbe.__nextLeakServedRequests = (requestProbe.__nextLeakServedRequests ?? 0) + 1;
|
|
106
|
+
});
|
|
107
|
+
return server;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
91
111
|
// src/bootstrap.ts
|
|
92
112
|
var workDir = process.env["NEXT_LEAK_DIR"];
|
|
113
|
+
function installRequestProbe() {
|
|
114
|
+
requestProbe.__nextLeakServedRequests = 0;
|
|
115
|
+
countServedRequests(http2);
|
|
116
|
+
countServedRequests(https);
|
|
117
|
+
}
|
|
93
118
|
if (workDir !== void 0 && workDir !== "") {
|
|
94
119
|
try {
|
|
95
120
|
await mkdir(workDir, { recursive: true });
|
|
96
121
|
const server = await startControlServer({ snapshotDir: workDir });
|
|
122
|
+
installRequestProbe();
|
|
97
123
|
await writeFile(
|
|
98
124
|
path2.join(workDir, `control-${process.pid}.json`),
|
|
99
125
|
JSON.stringify({ port: server.port, pid: process.pid })
|
|
@@ -106,13 +106,17 @@ function classifyMemoryTrend(heapSamples, externalSamples, options = {}) {
|
|
|
106
106
|
const external = classifyTrend(externalSamples, options);
|
|
107
107
|
const severity = {
|
|
108
108
|
leak: 0,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
pressure: 1,
|
|
110
|
+
inconclusive: 2,
|
|
111
|
+
saturating: 3,
|
|
112
|
+
stable: 4
|
|
112
113
|
};
|
|
113
114
|
if (severity[external.verdict] < severity[heap.verdict]) {
|
|
114
115
|
return { ...external, source: "external" };
|
|
115
116
|
}
|
|
117
|
+
if (severity[external.verdict] === severity[heap.verdict] && external.growthPerCycle > heap.growthPerCycle) {
|
|
118
|
+
return { ...external, source: "external" };
|
|
119
|
+
}
|
|
116
120
|
return heap;
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -125,7 +129,8 @@ var VERDICT_WEAKENING = /* @__PURE__ */ new Set([
|
|
|
125
129
|
"near-threshold",
|
|
126
130
|
"spiky-growth",
|
|
127
131
|
"thin-evidence",
|
|
128
|
-
"repetitions-disagree"
|
|
132
|
+
"repetitions-disagree",
|
|
133
|
+
"cache-residency"
|
|
129
134
|
]);
|
|
130
135
|
function warrantsIssueDraft(report) {
|
|
131
136
|
return effectiveVerdict(report) === "leak" && !report.confidence.warnings.some((warning) => VERDICT_WEAKENING.has(warning.code));
|
|
@@ -275,6 +280,15 @@ function thinEvidenceWarnings(trend, minGrowth) {
|
|
|
275
280
|
detail: `judged on ${trend.deltas.length} cycles, and its weakest grew ${mb(smallest)} against a ${mb(minGrowth)} gate \u2014 on that few cycles ordinary oscillation reaches that, and a repeat run often disagrees; measure more cycles to resolve it`
|
|
276
281
|
}];
|
|
277
282
|
}
|
|
283
|
+
function cacheResidencyWarnings(trend, revalidatesFromCache) {
|
|
284
|
+
if (trend.cacheDriven !== true || trend.verdict !== "leak" || revalidatesFromCache !== true) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
return [{
|
|
288
|
+
code: "cache-residency",
|
|
289
|
+
detail: `every request asked this ISR route for a key it had never cached, so storing them is part of the ${mb(trend.growthPerCycle)}/cycle \u2014 and here the usual remedy does not apply: bounding the keys ({n%N}) hands the requests back to the ISR cache, so the run would drive revalidation and measure Next's other path rather than a cleaner version of this one. Run it both ways and compare before attributing this to anyone`
|
|
290
|
+
}];
|
|
291
|
+
}
|
|
278
292
|
function isVerdictInvalid(input) {
|
|
279
293
|
if (input.trend.verdict !== "leak") {
|
|
280
294
|
return false;
|
|
@@ -295,6 +309,7 @@ function assessConfidence(input) {
|
|
|
295
309
|
...growthShapeWarnings(input.trend),
|
|
296
310
|
...noiseFloorWarnings(input.trend, minGrowth),
|
|
297
311
|
...thinEvidenceWarnings(input.trend, minGrowth),
|
|
312
|
+
...cacheResidencyWarnings(input.trend, input.revalidatesFromCache),
|
|
298
313
|
...heapCeilingWarnings(input),
|
|
299
314
|
...warmUpBaselineWarnings(input)
|
|
300
315
|
];
|