nesting structure
rule
use nesting only when it reveals multiple focused parts of one concept. a folder's index.ts is encouraged as that folder's pure barrel — the only allowed purpose for an authored index file. collocation precedes nesting: a block with exactly one consumer lives inside or beside that consumer; no folder is earned until a second consumer or a set of focused parts appears.
why nesting must be earned
a folder is navigation cost: every reader pays one hop to enter it, and a tree of single-file folders makes humans open wrappers before behavior. a folder exists only when it names one concept whose parts are independently understandable — a schema, a mapper, an operation that change for different reasons. symmetry with a sibling folder is not a reason to nest.
| observed shape | decision | reason |
|---|---|---|
| one concept, one authored file | denest to the parent as one file. | the folder hop buys nothing. |
| a block only its one consumer imports | keep it inside or beside that consumer. | collocation precedes nesting. |
| one concept, several independently changing leaves | nest under the concept name with a pure barrel. | the folder names the concept; leaves stay focused. |
| a wrapper folder that only forwards to one leaf | remove it. | the reader opens a layer with no behavior. |
| files grouped only by visual symmetry | keep them flat. | nesting must reflect ownership, not layout taste. |
barrel rule
index.ts exports the folder's public canonical symbols without renaming, wrapping, side effects, or implementation. consumers import the folder's public API; leaf files remain the direct owners of behavior.
workflowStep/index.tsreadWorkflowStep.tsworkflowStepMapper.tsworkflowStepSchema.ts
denest a single-file folder
one authored file in a folder → remove the folder, promote the file to its parent: workflowStep.ts, not workflowStep/index.ts. nesting earns its navigation cost only by exposing multiple real leaves.
break down an oversized file
when one file owns independently understandable responsibilities, create a same-named folder, move each responsibility to a focused leaf, add a pure index.ts barrel: split workflowStep.ts into workflowStep/readWorkflowStep.ts, workflowStep/workflowStepSchema.ts, workflowStep/workflowStepMapper.ts.
the same promotion applies to a consumer file that has accumulated single-consumer blocks: checkout.ts becomes checkout/ with its extracted parts as leaves — structure grows from usage instead of being imposed.
good and bad
bad
workflowStep/index.ts // only forwards to the single leafworkflowStep.tscharge/charge.ts // nested for symmetry with a sibling folder
each folder adds one navigation hop while exposing a single leaf — nesting has not earned its cost. wrapper layers carry no behavior, every move and import gains a path segment, and readers learn to skip folders because half contain nothing — exactly when real structure stops being trusted and grep replaces navigation.
good
workflowStep.ts # one concept, one file: denestedworkflowStep/ # multiple independently changing leavesindex.tsreadWorkflowStep.tsworkflowStepMapper.tsworkflowStepSchema.ts
nesting appears only where one concept has several focused parts; index.ts stays a pure barrel of those leaves. every hop lands on real behavior, the folder name carries meaning the leaves share, consumers get one import path per concept while leaves keep direct ownership — the tree stays an honest index of ownership.
do not
- put implementation, side effects, or a second unrelated concept in
index.ts. - add a folder for visual symmetry or one leaf file.
- create nested layers that make a human open wrappers before real behavior.
- rename a re-exported symbol; the barrel preserves the leaf symbol exactly.