C.W.K.
Stream
Lesson 07 of 07 · published

성장에서 살아남는 프로젝트 구조

~14 min · architecture, file-structure, organization

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
공식 React 파일 구조는 없어. 그게 feature 야. 비용은 본인이 골라야 한다는 것 — 잘못 고르면 빠르게 썩어.

두 가지 조직 축

모든 React 프로젝트가 (의식하든 안 하든) 두 축에서 위치 골라:

  1. type 별 vs feature 별. type-first 는 hook 다 hooks/, 컴포넌트 다 components/, 유틸 다 lib/. feature-first 는 feature 의 hook + 컴포넌트 + 유틸을 features/chat/, features/auth/ 에 같이.
  2. flat vs deep. flat 은 nesting 1-2 단계; deep 은 5-6 단계까지.

초기엔 type-first 가 이김 (카테고리로 찾기 쉬움). 나중엔 feature-first 가 이김 (feature 통째로 삭제/추출 쉬움). 전환점은 컴포넌트 50개 즈음.

cwkPippa 프런트엔드 레이아웃

cwkPippa 프런트엔드는 feature-first, 2 단계 깊이:

src/
  App.tsx              # 상태 lifting + 라우트 마운트
  main.tsx             # 엔트리
  index.css            # Tailwind v4 + 테마 토큰
  components/
    chat/              # InputArea, MessageList, MessageItem
    sidebar/           # ConversationList, FolderTree
    council/           # Council UI surface
    admin/             # admin 패널
    settings/          # settings UI
  hooks/               # useChat, useConversations, useHeartbeat
  lib/                 # api.ts, formatter, 상수
  types/               # 공유 TS 타입

주목: 컴포넌트는 feature 별 (chat/, sidebar/, council/) 인데 hook + 타입은 공유라서 type-first 야. 그 hybrid 가 정상 — 디렉토리마다 올바른 축 골라.

State-lifting 질문

cwkPippa 의 App.tsx 는 state-lifting root: 최상위 state 가 거기 살고 자식 컴포넌트는 props + callback 받음. 어떤 프로젝트는 state 를 Zustand store / Context provider 로 미는 대신. 정답 없어 — 다만 일관성 중요. 하나 골라 유지.

Path alias

긴 상대 경로 import (../../../components/chat/MessageList) 는 smell. Vite 가 tsconfig.jsonpaths 필드로 path alias 지원. @/*src/* 로 맞추면 import 가 @/components/chat/MessageList — 읽기 쉽고 리팩토링에 강해.

이 파일을 찾고 싶을 폴더. 새 파일 만들 때 물어봐: '3개월 뒤 이 파일 찾으러 어디 먼저 가 볼까?' 거기에 놔. 답이 'grep 할 거야' 면, 구조 이미 깨진 거야.

Code

tsconfig.app.json — path alias 셋업·json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
vite.config.ts — 매칭되는 Vite resolver·ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "node:path";

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
Before / after — 읽히는 import·tsx
// Before: 깨지기 쉬움, 파일 이동에 약함
import { MessageList } from "../../../components/chat/MessageList";
import { useChat } from "../../../hooks/useChat";

// After: 이동에 안정
import { MessageList } from "@/components/chat/MessageList";
import { useChat } from "@/hooks/useChat";

External links

Exercise

tsconfig.app.jsonvite.config.ts 양쪽에 @/* path alias 셋업. App.tsx 와 샘플 컴포넌트를 feature-first 또는 type-first 축에 따른 디렉토리로 이동. 최소 import 한 줄을 alias 쓰게 리팩토링. 그다음 일부러 컴포넌트 파일을 다른 디렉토리로 이동해서 alias 기반 import 가 그대로 동작하는지 vs 상대 경로면 깨졌을지 비교.
Hint
에디터에선 import 가 깨지는데 dev server 가 돌면 Vite 만 alias 있음. 에디터에선 동작하는데 런타임에 fail 이면 tsconfig 만 있음. 둘이 일치해야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.