Cursor frequently generates circular imports in Node.js projects because it resolves imports based on type proximity rather than module dependency graphs. This tutorial shows how to add import rules to .cursorrules, structure your project with a clear dependency hierarchy, and use Cursor to detect and fix existing circular dependencies.
Fixing broken imports from Cursor in Node.js projects
Circular imports cause undefined values, mysterious runtime errors, and test failures that are hard to debug. Cursor generates them frequently because it picks the nearest matching export without considering the full dependency graph. This tutorial teaches you how to configure Cursor to respect your module hierarchy and fix existing circular dependencies.
Prerequisites
- Cursor installed with a Node.js or TypeScript project
- Project with multiple modules that import from each other
- Basic understanding of ES modules or CommonJS imports
- Familiarity with Cmd+L and Cmd+I in Cursor
Step-by-step guide
Detect existing circular imports
Detect existing circular imports
Use Cursor Chat to analyze your project's import graph and find circular dependencies. This gives you a map of existing problems before setting up prevention rules.
1// Cursor Chat prompt (Cmd+L, Ask mode):2// @codebase Analyze the import statements across all3// .ts and .js files in src/. Identify any circular4// import chains where module A imports from B and B5// imports from A (directly or through intermediaries).6// List each circular chain with the full import path.78// You can also use a CLI tool for verification:9// npx madge --circular --extensions ts src/Expected result: A list of circular import chains in your project, showing which files are involved in each cycle.
Define an import hierarchy in .cursor/rules
Define an import hierarchy in .cursor/rules
Create rules that establish a clear dependency direction. This prevents Cursor from generating imports that flow in the wrong direction, which is the root cause of circular dependencies.
1---2description: Import hierarchy rules to prevent circular dependencies3globs: "src/**/*.ts"4alwaysApply: true5---67## Import Hierarchy (top imports from bottom, NEVER reverse)81. src/routes/ — imports from services, middleware92. src/services/ — imports from repositories, utils103. src/repositories/ — imports from models, utils114. src/models/ — imports from types only125. src/middleware/ — imports from services, utils136. src/utils/ — imports from types only, NO other internal imports147. src/types/ — ZERO internal imports (leaf module)1516## Rules17- NEVER import from a higher layer (services must NOT import from routes)18- NEVER create mutual imports between files in the same directory19- Shared types go in src/types/, not in service or route files20- If two modules need each other, extract the shared dependency into src/types/ or src/utils/21- Use dependency injection instead of direct imports for cross-cutting concernsExpected result: Cursor follows the import hierarchy and refuses to generate imports that violate the dependency direction.
Extract shared types to break cycles
Extract shared types to break cycles
The most common circular import pattern is two modules that share types. Fix this by extracting shared interfaces into a dedicated types file. Use Composer (Cmd+I) to automate the extraction.
1// Composer prompt (Cmd+I):2// @src/services/userService.ts @src/services/orderService.ts3// These two files have circular imports because they both4// define and use shared types. Extract all shared interfaces5// and types into src/types/shared.ts. Update both files to6// import from the new types file. Remove the circular import.78// Before (circular):9// userService.ts: import { Order } from './orderService'10// orderService.ts: import { User } from './userService'1112// After (fixed):13// types/shared.ts: export interface User {...} export interface Order {...}14// userService.ts: import { User, Order } from '@/types/shared'15// orderService.ts: import { User, Order } from '@/types/shared'Pro tip: Move type/interface definitions to src/types/ as a general practice. This eliminates the most common source of circular imports.
Expected result: Shared types extracted to a leaf module, breaking the circular dependency chain.
Use dependency injection for runtime circular needs
Use dependency injection for runtime circular needs
When two services genuinely need to call each other at runtime, use dependency injection instead of direct imports. Ask Cursor to refactor the mutual dependency into constructor injection.
1// Cursor Chat prompt (Cmd+L):2// @src/services/userService.ts @src/services/notificationService.ts3// These services call each other: userService sends notifications,4// notificationService looks up user preferences. Refactor to use5// dependency injection. Pass the dependency through the constructor6// instead of importing directly.78// Expected pattern:9class UserService {10 constructor(private notificationService: INotificationService) {}11 12 async deleteUser(id: string) {13 await this.notificationService.send(id, 'Account deleted');14 }15}1617class NotificationService {18 constructor(private userService: IUserService) {}19 20 async send(userId: string, message: string) {21 const prefs = await this.userService.getPreferences(userId);22 // send based on prefs23 }24}Expected result: Both services depend on interfaces instead of concrete implementations, eliminating the circular import.
Verify the fix with madge or Cursor re-audit
Verify the fix with madge or Cursor re-audit
After restructuring, verify that all circular imports are resolved. Run madge again or ask Cursor to re-check the import graph.
1// Terminal:2npx madge --circular --extensions ts src/34// Or Cursor Chat (Cmd+L):5// @codebase Re-check for circular imports in src/.6// Verify that the import hierarchy is respected:7// routes -> services -> repositories -> models -> types.8// Report any remaining violations.Expected result: Zero circular imports detected. All imports follow the established hierarchy.
Complete working example
1---2description: Import rules to prevent circular dependencies3globs: "src/**/*.{ts,tsx,js,jsx}"4alwaysApply: true5---67## Module Dependency Hierarchy8Imports flow DOWN this list only. Never import upward.9101. src/app/ or src/routes/ (entry points)11 - May import: services, middleware, types122. src/middleware/13 - May import: services, utils, types143. src/services/15 - May import: repositories, utils, types16 - NEVER import from: routes, middleware, other services (use DI)174. src/repositories/18 - May import: models, utils, types19 - NEVER import from: services, routes205. src/models/21 - May import: types only226. src/utils/23 - May import: types only24 - MUST be pure utilities with zero internal dependencies257. src/types/26 - ZERO internal imports (leaf node)27 - Contains all shared interfaces, enums, and type aliases2829## Anti-patterns to AVOID30- Two files importing from each other (A -> B -> A)31- Service A importing Service B directly (use interfaces + DI)32- Inline type definitions that create import dependencies33- Re-exporting from barrel files that create hidden cycles3435## How to fix circular imports361. Extract shared types to src/types/372. Use dependency injection for runtime dependencies383. Create an interface in src/types/ and depend on the interface394. Use events/callbacks instead of direct method callsCommon mistakes when fixing broken imports from Cursor
Why it's a problem: Creating barrel index.ts files that re-export everything
How to avoid: Use specific file imports instead of barrel imports in services. Reserve barrel files for types/ and components/ only.
Why it's a problem: Defining types inline in service files instead of in types/
How to avoid: Move all shared interfaces to src/types/. Add a rule: 'Shared types go in src/types/, not in service files.'
Why it's a problem: Ignoring the error because the code 'works in development'
How to avoid: Treat circular import warnings as errors. Run npx madge --circular in your CI pipeline to catch them early.
Best practices
- Define a clear module dependency hierarchy in .cursor/rules and enforce it in code reviews
- Keep types/ as a leaf module with zero internal imports
- Use dependency injection for cross-service communication instead of direct imports
- Run npx madge --circular in your CI pipeline to catch circular imports automatically
- Extract shared interfaces to dedicated type files rather than defining them in service files
- Use specific file imports over barrel index.ts files in service layers
- Start a new Cursor Chat session when working on import restructuring to avoid context pollution
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Node.js TypeScript project where userService.ts imports from orderService.ts and orderService.ts imports from userService.ts, creating a circular dependency. Both share User and Order types. Refactor this: extract shared types to a types file, use dependency injection for runtime dependencies, and ensure the import hierarchy follows routes -> services -> repositories -> types.
In Cursor Chat (Cmd+L): @codebase @.cursor/rules/imports.mdc Find all circular import chains in src/. For each cycle, suggest a fix: extract shared types to src/types/, use dependency injection for runtime needs, or restructure the module hierarchy. Show the specific import changes for each fix.
Frequently asked questions
Why does Cursor create circular imports?
Cursor resolves imports based on type matching, not dependency graphs. If ServiceB has the type ServiceA needs, Cursor imports from ServiceB regardless of whether ServiceB already imports from ServiceA.
Will TypeScript catch circular imports at compile time?
TypeScript's compiler does not error on circular imports. It resolves them silently, which can cause undefined values at runtime. You need a tool like madge or a Cursor audit to detect them.
Are circular imports always bad?
In most cases, yes. They indicate unclear module boundaries and can cause subtle runtime bugs. Rare exceptions exist in tightly coupled modules that are always loaded together, but these should be merged into a single module instead.
How do I fix circular imports in React component files?
Extract shared component types to a types file. If two components render each other (mutual recursion), use React.lazy() for one of them or restructure the component hierarchy so the dependency flows in one direction.
Can Cursor automatically fix circular imports across my project?
Use Composer Agent mode (Cmd+I) with the prompt: 'Fix all circular imports in src/ by extracting shared types to src/types/ and using dependency injection for runtime dependencies. Process one circular chain at a time.' Review each fix before accepting.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation