EOW Dev Log #1: Circular Dependency Nightmare & Colocation Wins π

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? π



