chore(benchmarks): record the server require graph, and use it to attribute boot-time code

A flat module cache says 24 MiB of source is resident before the first request, but not
what pulled any of it in. Recording parent -> child as the CommonJS loader resolves it
answers that, and the answer redirects the remaining 'ship less server JS' work:

  - Next loads all 74 app route bundles at startup, not lazily.
  - One 16.10 MiB / 460-file subtree is reached by the tRPC/API routes AND by
    instrumentation, so instrumentation's exclusive cost is 0 bytes - deferring it would
    free nothing, because Next preloads /api/[...trpc] anyway.
  - Only 1.10 MiB is common to every route, so this is the API surface pulling in every
    integration, not a universal barrel.
This commit is contained in:
Thomas Camlong committed 2026-08-09 21:39:28 +02:00
1 parent 3c56eab58f
commit 4766653979
3 files changed
+170 -1

No files matched your search

+31
View File
@@ -588,6 +588,37 @@ A prediction worth recording, because it confirms the mechanism rather than just
outcome: at `pageSize=1000` the largest allocation measured 0.7 MiB, and at 5,000 it
measured 2.4 MiB — 5× the page size, 3.4× the bytes, exactly as a per-page bound implies.
## Why 24 MiB of code is resident before the first request
The module cache is a flat list with no parentage, so "what pulled this in" needed the
require graph recorded as the loader resolves it (`HOMARR_PROBE_TRACK_REQUIRES=1`, analysed
by `scripts/benchmarks/analyze-require-graph.mjs`). At boot idle, having served only
`/api/health/ready`: **2,004 edges over 1,121 nodes, 23.97 MiB of source**.
| importer | subtree |
| --- | --- |
| `apps/nextjs/server.js` (everything) | 23.83 MiB / 1,087 files |
| `next/dist/server/load-components.js` | 21.75 MiB / 796 files |
| **`[root-of-the-server]__0g06wx7._.js`** | **16.10 MiB / 460 files** |
| next's own framework code, exclusively | 2.16 MiB / 315 files |
Two things follow, and the second is the one that matters:
1. **Next loads all 74 app route bundles at startup**, not on first request. That is
framework behaviour, not something this codebase sets.
2. **One subtree of 16.10 MiB / 460 files is reached by the tRPC/API routes *and* by
`instrumentation`** — the cron tasks and websocket server. Exclusive attribution for
instrumentation is therefore **0 bytes**: everything it touches, an API route touches
too. Only 1.10 MiB / 119 files is common to *every* route, so this is not one universal
barrel — it is specifically the API surface pulling in every integration and widget
request handler.
That kills the obvious fix. Deferring `instrumentation` would free nothing, because Next
preloads `/api/[...trpc]` at boot and that route reaches the same graph. Cutting this
requires the tRPC router to stop referencing every integration eagerly — the same lever the
integrations barrel diet started, with roughly 16 MiB of source still behind it. Recorded
as measured and understood, not attempted here.
## Measured but not pursued