DOCS/book/300_architecture/313_프론트_구조_원칙.md
happybell80 60a892e5ab fix: DOCS 내 0_VALUE 참조를 GitHub URL → 로컬 상대경로로 전환, 02_Governance → 20_Governance 수정 #33 #34
SSOT는 로컬 0_VALUE/. GitHub URL은 복사본 참조로 SSOT 원칙 위반.
02_Governance는 존재하지 않는 구 경로로 전부 깨진 링크.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 14:00:21 +09:00

12 KiB

React 프론트엔드 구조 원칙

작성일: 2025-11-29 수정일: 2026-01-21 (컴포넌트 분리 기준 300줄 → 500줄 완화) 상위 원칙: 0_VALUE Coding Principles 참고: 311_FastAPI_구조_원칙.md, 312_writing-principles.md

1. 컴포넌트 설계 원칙

Single Responsibility Principle (SRP)

  • 각 컴포넌트는 하나의 책임만 수행
  • 큰 컴포넌트는 작은 서브 컴포넌트나 훅으로 분리
  • UI 렌더링과 비즈니스 로직 분리

컴포넌트 분리 기준

  • 500줄 이상: 서브 컴포넌트 또는 커스텀 훅으로 분리 검토
  • 1000줄 이상: Monolithic Component로 간주, 리팩토링 필수
  • UI + API + State + Events 모두 혼재: 계층 분리 필요

2. 비즈니스 로직 분리

Custom Hooks 패턴

  • 데이터 fetching 로직: useFetch, useQuery 등 커스텀 훅으로 추출
  • 상태 관리 로직: useState 복잡도가 높으면 커스텀 훅으로 분리
  • UI 컴포넌트는 렌더링과 이벤트 핸들링만 담당

계층 분리

UI Components (렌더링)
    ↓
Custom Hooks (비즈니스 로직)
    ↓
API Services (통신)
    ↓
State Management (전역 상태)

3. 상태 관리 원칙

Immutability

  • 상태 직접 변경 금지: 항상 새로운 객체/배열 생성
  • 예측 가능한 상태 업데이트 보장
  • React 리렌더링 최적화

상태 관리 전략

  • 컴포넌트 내부 상태: useState (로컬 UI 상태)
  • 전역 상태: Context API 또는 상태 관리 라이브러리 (Zustand/Recoil)
  • 서버 상태: React Query, SWR 등 전용 라이브러리

Prop Drilling 회피

  • 3단계 이상 props 전달 시 Context API 또는 상태 관리 도입
  • 불필요한 중간 컴포넌트 통과 제거

4. 파일 및 폴더 구조

권장 구조

src/
├── components/          # 재사용 가능한 UI 컴포넌트
│   ├── ui/             # 기본 UI 컴포넌트 (버튼, 입력 등)
│   └── features/       # 기능별 컴포넌트
├── hooks/              # 커스텀 훅
├── services/           # API 통신 로직
├── stores/             # 전역 상태 관리 (선택)
├── utils/              # 유틸리티 함수
├── types/              # TypeScript 타입 정의
└── pages/              # 페이지 컴포넌트

파일 명명 규칙

  • 컴포넌트: PascalCase.tsx (예: ChatInterface.tsx)
  • 훅: use 접두사 + PascalCase.ts (예: useChat.ts)
  • 유틸: camelCase.ts (예: formatDate.ts)
  • 타입: PascalCase.ts (예: Message.ts)

5. 네이밍 규칙

명확한 네이밍

  • 구체적이고 설명적인 이름 사용
  • 축약어 지양, 의도가 명확한 이름
  • 예: handleSendMessage > handleSend, chatHistory > data

변수/함수 네이밍

  • 컴포넌트: PascalCase
  • 함수: camelCase, 동사로 시작 (handle, fetch, update)
  • 상수: UPPER_SNAKE_CASE
  • boolean: is, has, should 접두사

6. 코드 품질 원칙

useEffect 의존성 관리

  • 의존성 배열 정확히 명시
  • 중복 호출 및 무한 루프 방지
  • cleanup 함수 적절히 사용
  • Custom Hook 함수 반환: custom hook에서 반환하는 함수는 useCallback으로 메모이제이션 필수 (의존성 배열에 포함 시 무한 루프 방지)
  • Context 값 참조 시 useRef 사용: useEffect에서 Context 값(book, user 등)을 참조해야 할 때, 의존성 배열에 포함하면 무한 루프 발생 가능. useRef로 최신 값을 참조하되 의존성 배열에는 포함하지 않음

성능 최적화

  • 불필요한 리렌더링 방지: React.memo, useMemo, useCallback 적절히 활용
  • 대용량 리스트: 가상화 (virtualization) 고려
  • 이미지/리소스: lazy loading 적용

프론트엔드/백엔드 상태 동기화

  • 기본값 일치 확인: 상태 관련 기본값 변경 시 프론트엔드와 백엔드 모두 확인
  • 초기 상태 동기화: state_service.py의 초기값과 프론트엔드 기본값 일치 확인
  • 섹션/페이지 상태: 프론트엔드에서 사용하는 section/page 기본값과 백엔드 초기값 일치
  • 체크리스트: 상태 기본값 변경 시 프론트엔드/백엔드 코드 모두 검토

타입 안전성

  • TypeScript 타입 정의 명확히
  • any 타입 최소화
  • 인터페이스/타입 재사용

7. 금지 사항

  • 하드코딩된 값: 상수, 설정값은 환경변수 또는 설정 파일로 관리
  • Monolithic Component: 1000줄 이상 컴포넌트
  • 컴포넌트 내 비즈니스 로직: 커스텀 훅으로 분리
  • 상태 직접 변경: Immutability 원칙 위반
  • console.log 디버그 코드: 프로덕션 코드에 남기지 않음
  • 순환 import
  • Prop Drilling: 3단계 이상 전달 시 상태 관리 도입
  • 브라우저 캐시 언급 금지: 문제 해결 시 브라우저 캐시를 언급하지 말 것. 실제 문제는 코드/API 응답 구조일 가능성이 높음

8. 환경별 코드 분리 원칙

핵심 원칙: 개발/프로덕션 환경 구분 필수

  • 개발용 기능은 process.env.NODE_ENV === "development" 조건으로 숨김
  • 프로덕션 배포 전 개발 기능 노출 여부 확인
  • 환경별 다른 동작이 필요한 경우 조건부 렌더링/실행

9. 에러 처리 일관성 원칙

핵심 원칙: 환경별 일관된 에러 로깅 및 HTTP 상태 코드별 처리

  • console.error 대신 환경별 로깅 유틸리티 사용
  • 프로덕션 환경에서도 에러 로그 기록
  • 로깅 유틸리티는 개발/프로덕션 환경에 따라 적절히 처리

HTTP 상태 코드별 처리 규칙

에러 메시지 디자인 원칙: HTTP 레벨 에러와 사용자 메시지를 분리

  • 404 Not Found: 리소스가 아직 생성되지 않음 → 재시도/폴링 계속 (예: 평가 진행 중)
  • 504 Gateway Timeout: 게이트웨이 타임아웃 → 사용자에게 "분석 시간이 초과되었습니다" 또는 재시도 안내
  • 502 Bad Gateway: 백엔드 연결 실패 → 사용자에게 "서버 연결 오류" 안내, 재시도 제안
  • 500 Internal Server Error: 서버 오류 → 즉시 중단, 사용자에게 "서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요" 안내

금지 사항:

  • 모든 에러에 동일한 템플릿 메시지 사용 (예: "(템플릿) 평가 처리 중 오류가 발생했습니다")
  • HTTP 상태 코드를 사용자에게 직접 노출
  • 에러 원인 파악 없이 일반적인 메시지만 표시

권장 사항:

  • 에러 유형별로 명확한 사용자 메시지 제공
  • 원인 파악은 HTTP 상태 코드와 백엔드 로그로 교차 검증
  • 폴링/재시도 가능한 에러(404, 504)와 치명적 에러(500)를 구분하여 처리

10. 데이터 검증 원칙

핵심 원칙: 백엔드 응답 구조 검증 및 폴백 처리

  • 백엔드 응답 구조 검증 (빈 배열, 누락 필드)
  • 데이터 누락 시 사용자에게 명확한 안내 메시지 제공
  • 예상치 못한 데이터 구조에 대한 폴백 처리

배열/객체 필드 검증 필수

원칙: 배열 필드는 Array.isArray() 체크 후 사용, 객체 필드는 옵셔널 체이닝(?.) 사용, 기본값 설정 필수

금지 사항:

  • 배열 필드에 직접 .map() 호출 (null/undefined 체크 없이)
  • 객체 필드에 직접 속성 접근 (옵셔널 체이닝 없이)

11. 체크리스트

코드 작성 전/후 확인:

  • 각 컴포넌트가 단일 책임을 수행하는가?
  • 비즈니스 로직이 커스텀 훅으로 분리되었는가?
  • 상태 변경이 Immutability 원칙을 따르는가?
  • 파일 크기가 500줄 이하인가? (예외: 핵심 컴포넌트는 가급적 유지)
  • 하드코딩된 값이 없는가?
  • Prop Drilling이 3단계 이상인가? (상태 관리 필요)
  • TypeScript 타입이 명확히 정의되었는가?
  • useEffect 의존성이 정확한가?
  • Custom Hook 함수 반환 시 useCallback 사용했는가?
  • React Router basename 사용 시 Link 컴포넌트 상대 경로만 사용했는가?
  • JS/CSS 파일 로드 시 버전 파라미터(?v=YYYYMMDD) 추가 확인 (캐싱 문제 방지)

배포 전/후 확인:

  • 코드 변경 후 git status로 커밋되지 않은 변경사항 확인
  • 프론트엔드/백엔드 모두 배포 완료 확인
  • 배포 후 실제 브라우저에서 동작 확인 (추측하지 말고 직접 확인)
  • 네트워크 탭에서 API 호출 상태 확인 (200/500 등)

11. 라우팅 원칙

React Router basename 사용

  • basename 설정 시: BrowserRouterbasename 설정 후, 모든 Link 컴포넌트는 상대 경로만 사용
  • 절대 경로 하드코딩 금지: /goosefarm/blog 같은 절대 경로에 하위 경로를 하드코딩하지 않음 (basename과 중복되어 /goosefarm/goosefarm/blog 같은 잘못된 URL 생성)
  • 권장: Link to="/blog" (상대 경로), React Router가 basename과 자동 결합

12. 프론트엔드 캐싱 전략

버전 파라미터 필수 (수동 빌드 시)

  • JS/CSS 파일: ?v=YYYYMMDD 추가 (예: app.js?v=20251204)
  • 배포 후 버전 번호 업데이트 필수

nginx SPA index.html 캐시 방지 (Vite 빌드 시 필수)

  • Vite는 JS/CSS에 해시 파일명 자동 생성 (예: index-D3r66P5F.js)
  • index.html만 캐시 방지 필수: Cache-Control: no-store, no-cache, must-revalidate
  • index.html이 캐시되면 이전 해시 파일명 참조 → 배포 후 변경사항 미반영
  • nginx 설정 예시: location = /goosefarm/index.html 블록에 add_header Cache-Control 추가

nginx 캐시 제어 (일반)

  • ETag 사용 시 Ctrl+F5로도 캐시 회피 어려움
  • 버전 파라미터가 가장 확실한 방법

13. 레이아웃 최적화 원칙

사이드바 고정 패턴

  • 문제: 사이드바가 라우트 컴포넌트 내부에 있으면 라우트 변경 시 리렌더링됨
  • 해결: 사이드바를 App.tsx 레벨로 분리하여 Routes 밖에 배치
  • 구조:
    <BookProvider>
      <div className="flex">
        <PersistentSidebar /> {/* Routes 밖에 배치 */}
        <Routes>
          <Route path="/blog/:id" element={<BlogLayout />} />
        </Routes>
      </div>
    </BookProvider>
    
  • 장점: 라우트 변경 시에도 사이드바가 유지되고, 본문만 업데이트됨

Context 값 참조 최적화

  • 문제: useEffect 의존성 배열에 Context 값(book, user 등)을 포함하면 무한 루프 발생 가능
  • 해결: useRef로 최신 값을 참조하되 의존성 배열에는 포함하지 않음
  • 예시:
    const { book, setBook } = useBook()
    const bookRef = useRef(book)
    
    useEffect(() => {
      bookRef.current = book // 최신 값 유지
    }, [book])
    
    useEffect(() => {
      // bookRef.current로 참조, 의존성 배열에는 포함하지 않음
      if (bookRef.current?.id === blogData.book_id) {
        // 같은 book_id면 API 호출 생략
      }
    }, [id, setBook]) // book 제외
    

BookContext 객체 참조 유지

  • 문제: 같은 book_id인데도 매번 새로운 객체가 생성되어 불필요한 리렌더링 발생
  • 해결: BookContext에서 같은 book_id이고 chapters 배열이 같으면 객체 참조 유지
  • 구현: setBook 함수에서 book_idchapters 배열을 비교하여 동일하면 업데이트하지 않음

14. 참고

  • 상위 원칙: 이 문서는 일반적인 React 프로젝트 원칙을 다룹니다.
  • 프로젝트별 특화 규칙: 각 프로젝트의 README나 문서에 추가 규칙 명시
  • 백엔드 원칙: 311_FastAPI_구조_원칙.md 참고
  • 문서 작성 원칙: 312_writing-principles.md 참고