Module Boundaries¶
Nx enforces strict module boundaries through ESLint rules to maintain a clean architecture and prevent circular dependencies.
Boundary Rules¶
The module boundary system uses tags to control which projects can depend on others.
Tag System¶
graph TD
A[scope:petfolio-business<br/>Main App] --> B[scope:ui-shared<br/>Component Library]
A --> C[scope:styles<br/>Theme Utils]
B --> C
D[scope:docs<br/>Documentation]
style A fill:#4fc3f7
style B fill:#81c784
style C fill:#ffb74d
style D fill:#ba68c8
| Tag | Purpose | Can Import From |
|---|---|---|
scope:petfolio-business |
Main application | scope:ui-shared, scope:styles |
scope:ui-shared |
Shared UI components | scope:styles |
scope:styles |
Theme and styling | (none - leaf node) |
scope:docs |
Documentation | (none - isolated) |
ESLint Configuration¶
Boundaries are enforced in eslint.config.mjs:
Examples¶
✅ Valid Imports¶
| apps/petfolio-business/src/app/page.tsx | |
|---|---|
| libs/ui-component-library/src/lib/atoms/Button/Button.tsx | |
|---|---|
| libs/util-global-theme/src/lib/themes/default.ts | |
|---|---|
❌ Invalid Imports¶
| libs/ui-component-library/src/lib/atoms/Button/Button.tsx | |
|---|---|
| libs/util-global-theme/src/lib/themes/default.ts | |
|---|---|
| apps/petfolio-docs/mkdocs.yml | |
|---|---|
Why Module Boundaries?¶
1. Prevent Circular Dependencies¶
graph LR
A[App] --> B[UI Lib]
B -.->|❌ Blocked| A
style A fill:#4fc3f7
style B fill:#81c784
Without boundaries, projects could create circular dependencies that make builds fail.
2. Enforce Architectural Layers¶
graph TD
A[Application Layer<br/>petfolio-business] --> B[Shared UI Layer<br/>ui-component-library]
B --> C[Foundation Layer<br/>util-global-theme]
style A fill:#4fc3f7
style B fill:#81c784
style C fill:#ffb74d
Higher layers can depend on lower layers, but not vice versa.
3. Enable Independent Development¶
Teams can work on libraries without breaking consumers:
- Theme team works on
util-global-theme - Component team works on
ui-component-library - App team works on
petfolio-business
Changes to lower layers don't require changes to higher layers.
4. Improve Build Performance¶
Nx can cache and skip rebuilding unaffected projects:
Checking Boundaries¶
Lint Command¶
Visualize Dependencies¶
CI Enforcement¶
The CI pipeline automatically checks boundaries:
Pull requests cannot merge if boundary violations exist.
Adding New Projects¶
When creating a new project, assign appropriate tags:
| libs/my-new-lib/project.json | |
|---|---|
Then update ESLint config if you need new boundary rules:
| eslint.config.mjs | |
|---|---|
Common Violations¶
1. Importing from App into Library¶
Solution: Move shared config to a library.
2. Circular Dependencies¶
Solution: Restructure to avoid cycles.
3. Using Relative Paths Across Boundaries¶
Solution: Use path aliases (@ui-component-library).
Best Practices¶
!!! tip "Keep Libraries Focused" Each library should have a single, well-defined purpose.
!!! tip "Prefer Composition Over Inheritance" Build complex components from simple ones rather than creating deep hierarchies.
!!! tip "Export Through Barrel Files" Always export through index.ts to
control the public API.
!!! warning "Don't Bypass Boundaries" Never use eslint-disable to bypass
boundary rules - fix the architecture instead.