How can I profile Node.js module load time without patching require?
Asked by
MellowQuasar47
Our API server's cold startup became noticeably slower, causing deploy health checks to time out on smaller instances. The delay happens before the first request, not under normal load, and the commit history didn't make the regression obvious.
To investigate, I wrote a small preload script that wraps `Module._load`, measures each require with `process.hrtime.bigint()`, and logs calls taking longer than 50 ms. Cold startup averaged 1.9 seconds, with about 1.1 seconds coming from our `src/lib/index.js` barrel module. A route needed only one date helper, but importing the barrel loaded all 34 modules in that directory, including two that read configuration files during import. Replacing the barrel import with a direct import of the helper reduced cold startup to about 0.8 seconds.
Patching `Module._load` worked, but it feels brittle. Is there a runtime-supported way to get a similarly useful per-module startup breakdown, ideally without modifying the module loader?
Answers
Try Nodeโs `--cpu-prof` option. It generates a CPU profile without requiring a `Module._load` patch. If you bucket the `.cpuprofile` samples by `callFrame.url`, the top-level execution time for individual modules becomes visible, including modules doing expensive work while they are evaluated. It wonโt be a perfect replacement for timing every require, but itโs a good way to identify modules responsible for startup cost.
Reply to this answer
The biggest win is probably the fix you already found: avoid importing a broad barrel when the caller needs only one helper. Importing the date utility directly prevents the other modulesโand their import-time configuration workโfrom running during startup. More generally, keep module initialization lightweight and move filesystem reads or other setup behind an explicit initialization step when possible.
Reply to this answer
Add your answer
Answers are reviewed before publication.
PixelHarbor6: Be careful with synchronous config or file reads during import. Those often show up under a native filesystem frame rather than being attributed neatly to the JavaScript module, so the CPU profile can under-report that part of the startup time.