# Architecture Improvement

> Reorganize a project into a feature-based folder structure with unidirectional imports. Use when adding a new feature conflicts with existing structure, when teammates can't find files, when circular dependencies appear in build logs, or at the start of a new quarter. Not for changes within a single file (use code-refactoring) or component extraction (use component-quality).

- Skill: `jaykim88/architecture-improvement-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jaykim88/architecture-improvement-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jaykim88/architecture-improvement-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: JayKim88 (https://skillmd.com/u/jaykim88)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jaykim88/architecture-improvement-2

---


# Architecture Improvement

## Purpose
Establish clear module boundaries by feature, enforce unidirectional imports, and prevent circular dependencies. The folder structure should answer "where does this code live?" in under 10 seconds.

**Universal** — feature-based folder organization, unidirectional import rules, circular-dependency detection, and barrel-file discipline apply to any modular codebase. Server/Client component boundary is React-specific; other frameworks have analogous concerns.

## Procedure

**Precondition — is the migration worth it?** Feature-based organization pays off at scale, not in small apps. If the project is small (roughly < 20-30 components, 1-2 features) and none of the Triggers apply, keep the simpler layer-based layout — empty `features/x/{api,hooks,...}` shells add navigation cost with no benefit. Layer-based isn't obsolete; it's the right default until a Trigger appears (can't find files / one change spans many folders / cross-feature coupling causes bugs).

1. **Reorganize from layer-based to feature-based**
   - Layer-based (group by technical type): `components/`, `hooks/`, `utils/`, `types/`
   - Feature-based (vertical slice per domain): `features/auth/{api,components,hooks,stores,types,utils}/`
   - Each feature is a self-contained vertical slice; truly cross-cutting code stays in `shared/` (a hybrid layout is normal and expected)

2. **Define the import rule (unidirectional)**
   ```
   shared/  →  features/  →  app/
   ```
   - `shared/` can be imported by `features/` and `app/`
   - `features/` can be imported by `app/` only — never by another feature
   - `app/` (pages, routes) is the top — imported by nothing

3. **Detect and enforce the boundary (don't rely on discipline)**
   - Quick manual scan: grep for cross-feature imports
   - Persistent enforcement: a lint rule that fails CI on cross-feature or wrong-direction imports — discipline alone always erodes
   - Resolution per violation: promote the shared piece to `shared/`, OR establish a documented dependency (rare, requires ADR)
   - **Keep `shared/` generic, not a junk drawer** — the real failure mode of feature-based architecture. Promote only code with *no domain knowledge* (a Button, `formatDate`, an HTTP client); "used by 2 features" alone is not the bar. Domain-specific code shared by two features belongs to one feature, imported by the other via a documented exception. Sub-organize `shared/` (`ui` / `lib` / `api` / `config`) — never a flat dump.
   - See Implementation for the exact lint config per stack

4. **Detect and resolve circular imports (validation loop)**
   - Run a circular-dependency detector (`madge` for JS/TS — see Implementation)
   - For each cycle reported: break by extracting the shared dependency to a third module
   - Type-only cycles (e.g. `import type` in TS) are erased at compile time and don't break at runtime — fix real (value) cycles first; de-prioritize type-only ones
   - Re-run until value-level cycles = 0 (don't ship with runtime circular imports)

5. **Audit barrel files (`index.ts` re-exports)**
   - Keep barrels ONLY at the `shared/` public-API layer (consumed by many features)
   - Never at the feature root (`features/auth/index.ts`) — direct subpath imports instead: `from '@/features/auth/api/login'`
   - Barrel files defeat tree-shaking and create circular dep risk; the convenience never justifies it inside features

6. **Reposition server/client rendering boundaries** (frameworks with a server/client split)
   - Default to server rendering; mark a unit as client-only where it genuinely needs interactivity (component-local state, browser APIs, event handlers)
   - Audit for over-marking — units that don't actually use client features should drop the directive to stay on the server
   - Coordinate with `render-strategy-decision`
   - See Implementation for framework-specific detection (e.g., the `'use client'` audit in React/Next.js)

7. **Document the structure**
   - ADR: "Why feature-based" + import rule + barrel policy
   - `docs/architecture.md` with directory diagram

## Completion Criteria
- [ ] Cross-feature imports = 0 (or all documented in ADR)
- [ ] `madge --circular` reports 0
- [ ] Barrel files only at the 3+-consumer level
- [ ] ADR exists for the architecture decision

## Stop & Ask (AI must pause for user approval)

- **Before moving files across directory boundaries** — git history and IDE bookmarks break; user confirms the new layout first
- **Before introducing eslint-plugin-import enforcement** that would fail current CI — coordinate with team
- **Before promoting a shared piece to `shared/`** — verify it's actually used by 2+ features and won't be specialized later

## Output
- **Folder reorganization**: feature-based structure with documented import rule
- **ADR**: `docs/adr/ADR-NNN-feature-based-architecture.md` documenting the decision (Context / Options / Decision / Consequences)
- **ESLint config**: `eslint-plugin-import` with `no-restricted-paths.zones` enforcement
- **Migration commits**: one commit per moved subsystem; commit format `refactor(arch): move <subsystem> to feature-based layout`
- **Migration log** (paste into PR description): which features moved, which barrel files removed, which cycles broken

## Implementation

### React + Next.js (default)
- Folder: `app/`, `features/`, `shared/`, `lib/`
- Import enforcement: `eslint-plugin-import` `no-restricted-paths.zones` in `.eslintrc` (CI-blocking):
  ```json
  "import/no-restricted-paths": ["error", {
    "zones": [
      { "target": "src/features/*/!(index.ts)", "from": "src/features/*", "except": ["./"] },
      { "target": "src/shared", "from": "src/features" },
      { "target": "src/shared", "from": "src/app" }
    ]
  }]
  ```
  Quick manual scan: `grep -rE "from ['\"]@/features/[a-z]+/" src/features/`
- Circular detection: `npx madge --circular src/`
- Server/Client boundary: `grep -rn "'use client'" src/` — remove the directive from any file that uses no hooks (`useState`/`useEffect`/`useReducer`), no browser APIs (`window`/`document`/`localStorage`), and no React event handlers (`onClick` etc.)
- Barrels: avoid inside features; OK at `shared/` public API

### Other stacks
- **Vue / Nuxt**: Nuxt auto-imports across `composables/`, `components/`, `utils/` — feature boundary requires explicit configuration; `nuxt.config.ts` has `imports.dirs` for fine-grained control
- **SvelteKit**: feature folders under `src/lib/`; routes in `src/routes/` ARE the app layer; barrel discouraged because Vite tree-shakes individual exports well
- **Angular**: feature modules + standalone components; `eslint-plugin-import` + `@nx/eslint-plugin` for cross-feature enforcement; lazy-loaded routes via `loadChildren`
- **Universal**: `madge` works for any JS/TS project; feature-based organization (vertical slices) is a universal pattern from Domain-Driven Design; circular dependencies are a smell in any language

## Related skills
- `code-refactoring` — for changes within a single file
- `render-strategy-decision` — when reorganizing Server/Client component boundaries
- `component-quality` — for component extraction during the restructure

## Reference
- **Key insight encoded**: Enforce unidirectional imports — features may import only from `shared/`, never from sibling features. When two features need to share, the right move is to promote the shared piece up to `shared/`, not to barrel-export across siblings. Barrel files are convenient but cost tree-shaking and create circular dep risk; use them only where there's a stable 3+-consumer public API.

