Node.js Require Cycle - Fix "Cannot access before initialization"
When module A imports B and B imports A, one of them runs before the other has finished initializing. Under ESM that surfaces as a temporal-dead-zone error; under CJS it surfaces as an unexpectedly undefined import.
What this error means
A build or test crashes at module load with ReferenceError: Cannot access 'X' before initialization (ESM), or a value imported from a sibling module is mysteriously undefined (CJS). It is order-dependent, so it can appear only in CI where the entry path differs.
ReferenceError: Cannot access 'helper' before initialization
at file:///app/src/a.js:2:14
at ModuleJob.run (node:internal/modules/esm/module_job)
# (ESM live-binding evaluated before the cyclic module finished)Common causes
Two modules import each other at top level
A ↔ B import cycle means whichever loads first sees the other half-initialized. ESM throws a TDZ error; CJS yields a partially-populated module object (often undefined).
A barrel/index file re-exports into a cycle
An index.js that re-exports everything can route an import back through itself, creating a cycle that only bites under a particular load order.
How to fix it
Break the cycle
Extract the shared piece into a third module both sides import, removing the A↔B edge.
// before: a.js <-> b.js cycle
// after: move the shared symbol to shared.js
// a.js -> import { helper } from './shared.js'
// b.js -> import { helper } from './shared.js'Locate and confirm the cycle
- Run with
node --trace-warningsor a cycle detector (e.g. madge) to find the loop. - Avoid using a cyclic import’s value at module top level - defer it inside a function if you cannot restructure yet.
- Watch barrel/index re-exports, a common hidden source of cycles.
How to prevent it
- Keep dependency direction acyclic; push shared code into leaf modules.
- Run a cycle detector in CI for large module graphs.
- Be cautious with barrel files that re-export widely.