
EOW Dev Log #1: Circular Dependency Nightmare & Colocation Wins π
22 March 2025Β·3 min readΒ·8 views
This week, I faced circular dependency issues head-on while working on my bootstrapped real estate startup project.
A deep dive into feature colocation architecture revealed some unexpected blind spots.
Hereβs how it went:
π What is Feature Colocation?
Feature colocation means grouping related files inside feature-specific folders rather than scattering them across different directories.
This keeps:
-
Business logic close to UI & API calls
-
Code more maintainable & scalable
After reading TkDodoβs article on Colocation (where he also referenced Kent C. Doddsβ take on colocation), I structured my project like this:
/features/property βββ server.ts βββ client.ts βββ consts.ts βββ model.ts βββ types.ts βββ components/ βββ hooks/
All logic flows through these feature folders. Seemed perfect β until it wasnβt.
π Preventing Circular Dependencies
To stay ahead of circular dependency issues from the start, I:
-
Set up import/no-cycle ESLint rules.
-
Installed madge to detect dependency loops.
However⦠neither of them warned me. Not even once.
π¨ The Issue Appears
Everything seemed fine β until I pushed to GitHub after working on multiple features. π
-
The PR triggered a Vercel deploymentβ¦
-
Boom π₯ A build error: Invalid ESLint rule.
-
Turns out, my ESLint plugins config was wrong (I used an array instead of an object). π

π The Real Problem: Circular Dependencies
After fixing ESLint, I ran pnpm build locally⦠and guess what?
-
Over 100 files had circular dependency issues! π΅βπ«
-
I knew circular deps existed in theory but never had to deal with them at this scale (100+ files).
Below is what the circular dependency error looks like after running pnpm build:

π§ What Caused It?
My feature assets β like consts.ts, types.ts, model.ts β were cross-importing each other, causing a loop.
Example:
// consts.ts import { SomeType } from "./types"; // types.ts import { SOME_CONST } from "./consts"; // model.ts import { SomeType } from "./types"; import { SOME_CONST } from "./consts";
This caused a self-referencing loop, breaking the build.

π οΈ The Fix
I enforced a strict import hierarchy:
types.ts β‘οΈ consts.ts β‘οΈ model.ts // β Define types first; they should not import from consts (types.ts) // β Constants can import types but not models (consts.ts) // β Models can import both types and constants (model.ts)
-
π« No mutual imports!
-
π‘ Extract mutual imports into a dedicated module to break cycles.
-
β‘οΈ Clear, one-directional data flow.
After implementing the above fixes, the project finally builds successfully locally β

β And on Vercel

In total, I made changes to 147 files to resolve the circular dependency issue.

π― Lessons Learned
-
Feature colocation reduces mental load & makes refactoring easier.
-
Always validate ESLint rule configurations before trusting them
-
Prototype errors early to verify expected behavior.
-
Push more often β frequent commits & early deployments help catch issues sooner.
- Vercel catches what local dev setups might miss β deploy early to uncover hidden problems.
π‘ Final Thoughts
Despite the struggles, colocation made my architecture cleaner & more scalable. Iβd still choose it again for future projects.
Have you ever dealt with circular dependencies at scale? How did you fix them? π