--- id: wiki-2026-0508-folder-structure-best-practices title: Folder Structure Best Practices category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Project Structure, Code Organization, Frontend Folder Layout] duplicate_of: none source_trust_level: A confidence_score: 0.85 verification_status: applied tags: [architecture, folder-structure, frontend, project-organization] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react-next --- # Folder Structure Best Practices ## 매 한 줄 > **"매 folder structure 의 골 — colocation by feature, not by file-type"**. 매 modern frontend (2026) 의 favored: feature-sliced design, screaming architecture, Next.js app router colocation. 매 type-based folders (`components/`, `utils/`) 의 rejection — feature 의 boundary 의 unclear. ## 매 핵심 ### 매 Type-based (anti) vs Feature-based (good) - **Type-based**: `/components`, `/hooks`, `/utils`, `/services`, `/types` — every change touches 5 folders. - **Feature-based**: `/features/checkout/{components,hooks,api,types}` — change scope = single folder. ### 매 Levels of Hierarchy 1. **Routes / pages** (Next.js `app/`, Remix `routes/`). 2. **Features** (`features//...`) — domain boundaries. 3. **Shared** (`lib/`, `shared/`, `ui/`) — cross-feature reusables. 4. **Entities/domain** (Feature-Sliced: `entities/user`, `entities/cart`). ### 매 Boundaries - Features 의 each other 의 import X (cross-feature 의 shared layer 의 통과). - Lower layers 의 higher 의 import X (UI 의 feature 의 import X). - 매 ESLint `no-restricted-imports` 또는 `eslint-plugin-boundaries` 의 enforce. ### 매 응용 1. Next.js 15 app router project. 2. Vite + React SPA. 3. Monorepo (Turborepo, Nx). 4. React Native app. ## 💻 패턴 ### Feature-Sliced (recommended 2026) ``` src/ ├── app/ # app entry, providers, global styles ├── pages/ # (or routes/) page-level composition ├── features/ # business actions (toggle-cart, send-comment) │ └── send-comment/ │ ├── ui/ │ ├── model/ # state, hooks │ ├── api/ │ └── index.ts # public API ├── entities/ # business entities (user, post, cart) │ └── user/ │ ├── ui/ │ ├── model/ │ └── api/ ├── shared/ # reusable, domain-agnostic │ ├── ui/ # buttons, inputs (design system) │ ├── lib/ # utilities │ ├── api/ # base http client │ └── config/ └── widgets/ # composite UI blocks (Header, Sidebar) ``` ### Next.js 15 App Router (colocation) ``` app/ ├── layout.tsx ├── page.tsx ├── (marketing)/ # route group, no URL segment │ └── about/page.tsx ├── dashboard/ │ ├── layout.tsx │ ├── page.tsx │ ├── _components/ # private (underscore = not routable) │ │ └── Sidebar.tsx │ ├── settings/ │ │ ├── page.tsx │ │ └── actions.ts # server actions │ └── api/ │ └── route.ts src/ ├── lib/ # shared utilities, db, auth ├── components/ui/ # design system └── features/ # cross-page features ``` ### Vite + React SPA (mid-size) ``` src/ ├── main.tsx ├── App.tsx ├── routes/ # react-router routes ├── features/ │ └── auth/ │ ├── components/ │ ├── hooks/ │ ├── api.ts │ ├── types.ts │ └── index.ts ├── components/ui/ # design system ├── hooks/ # generic hooks ├── lib/ └── types/ ``` ### Monorepo (Turborepo) ``` apps/ ├── web/ # Next.js app ├── mobile/ # Expo └── docs/ packages/ ├── ui/ # shared design system ├── config/ # eslint, ts, tailwind presets ├── api/ # tRPC / generated client └── db/ # Prisma schema ``` ### Feature Public API (barrel) ```ts // features/checkout/index.ts — explicit exports only export { CheckoutPage } from './ui/CheckoutPage'; export { useCheckout } from './model/useCheckout'; export type { CheckoutState } from './model/types'; // internals (api/, model/internal) NOT re-exported ``` ### Boundary Enforcement (eslint-plugin-boundaries) ```js // eslint.config.js import boundaries from 'eslint-plugin-boundaries'; export default [{ plugins: { boundaries }, settings: { 'boundaries/elements': [ { type: 'shared', pattern: 'src/shared/*' }, { type: 'entity', pattern: 'src/entities/*' }, { type: 'feature', pattern: 'src/features/*' }, { type: 'widget', pattern: 'src/widgets/*' }, { type: 'page', pattern: 'src/pages/*' }, ], }, rules: { 'boundaries/element-types': ['error', { default: 'disallow', rules: [ { from: 'page', allow: ['widget', 'feature', 'entity', 'shared'] }, { from: 'widget', allow: ['feature', 'entity', 'shared'] }, { from: 'feature', allow: ['entity', 'shared'] }, { from: 'entity', allow: ['shared'] }, { from: 'shared', allow: ['shared'] }, ], }], }, }]; ``` ### TS Path Aliases ```json // tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@shared/*": ["src/shared/*"], "@features/*": ["src/features/*"] } } } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Small SPA (<20 components) | Flat or simple `components/` + `pages/` | | Mid-size (20-200) | Feature-based (`features/` + `shared/`) | | Large / team scale | Feature-Sliced Design + boundary lint | | Next.js project | App router colocation + `src/features/` | | Monorepo | Turborepo `apps/` + `packages/` | **기본값**: feature-based + shared layer + ESLint boundary enforcement. ## 🔗 Graph - 부모: [[Software Architecture]] · [[Large_Frontend_Projects|Frontend Architecture]] - 변형: [[Feature-Sliced Design]] · [[Atomic Design]] - 응용: [[Monorepo]] · [[Design System]] - Adjacent: [[Next.js]] · [[Turborepo]] ## 🤖 LLM 활용 **언제**: project structure 의 design, refactor folder layout, boundary rule 의 setup. **언제 X**: tiny prototype — overhead 의 not worth. ## ❌ 안티패턴 - **Type-based root** (`/components`, `/hooks`, `/utils`): scales poorly. - **Deeply nested barrels**: circular import / slow build. - **God `utils/`**: dump folder. 매 specific domain 의 split. - **Cross-feature imports**: feature isolation 의 break — shared 의 use. - **`index.ts` 의 every file의 leak**: tree-shaking break, public API 의 unclear. - **`pages/` 안에 business logic**: route 는 thin, feature 의 delegate. ## 🧪 검증 / 중복 - Verified (Feature-Sliced Design docs, Next.js project structure docs, Bulletproof React). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — folder structure best practices full content |