어떤 도메인에도 의존하지 않는 코드. UI 컴포넌트, 유틸, API 클라이언트.
상위: ../../CLAUDE.md
shared/
├── ui/ # 디자인 시스템 컴포넌트 (Button, Input, Modal, Badge, ...)
├── api/ # 자동 생성 타입, axios/fetch 클라이언트, 인터셉터
├── hooks/ # 도메인 비종속 훅 (useEventStream, useDebounce, ...)
├── lib/ # 추상화된 라이브러리 (AsyncBoundary, ErrorBoundary)
├── utils/ # 순수 유틸 함수 (date, format)
├── i18n/ # 번역 키
└── config/ # 환경 변수 wrapper
shared/* → shared/* ✓ (단 순환 금지)
shared/* → domain/* ✗
shared/* → features/* ✗
shared/* → pages/* ✗
shared/* → app/* ✗
shared는 가장 아래 레이어. 어떤 것도 import하지 않는다 (외부 라이브러리 제외).
도메인 개념이 들어가는 순간 → domain/으로 옮긴다.
| 케이스 | 위치 |
|---|---|
Button |
shared/ui |
JobCategoryBadge (직군별 색상) |
features/* (도메인 의존) — 또는 domain/user/ui (드물게) |
formatDate(date, format) |
shared/utils |
formatSessionDuration(session) |
domain/session/lib (도메인 타입 필요) |
useDebounce |
shared/hooks |
useSessionTimer(session) |
features/interview |
판단 기준: 도메인 타입을 import하면 domain/features. 안 하면 shared.
별도 ui/CLAUDE.md 참조.
shared/api/
├── client.ts # axios 인스턴스 (baseURL, interceptors)
├── auth.ts # 토큰 inject, refresh 로직
├── errors.ts # 에러 코드 enum, ApiError 클래스
├── generated.ts # OpenAPI 자동 생성 타입 (커밋 X 또는 O 정책 결정)
└── index.ts
원칙:
- 서버 baseURL, 헤더, 인증, retry/refresh는 client에 집중
- 도메인 API 호출은
features/{name}/api/에서 정의 (그 안에서 client 사용) - 응답 타입은
generated.ts우선, 없으면 도메인 타입으로 변환
권장 후보:
useEventStream(url, options)— SSE 추상화 (재연결, 폴링 fallback)useDebounce(value, ms)useThrottle(callback, ms)useMediaQuery(query)useLocalStorage<T>(key)useIsMounted()useClickOutside(ref, handler)
도메인 의존이 들어가면 features/{name}/model/로.
추상화 컴포넌트 (라이브러리 wrap):
AsyncBoundary/— Suspense + ErrorBoundary 합성 (이미 구현됨)ErrorBoundary/— 클래스 컴포넌트 (이미 구현됨)Portal/— Modal/Toast 등에서 사용Suspended/— Suspense + delay (깜빡임 방지)
date.ts—formatDate,relativeTime(date-fns 사용)format.ts—formatNumber,truncatearray.ts—groupBy,chunkobject.ts—pick,omitstring.ts—capitalize,slugify
lodash 전체 import는 회피. 필요한 함수만 직접 구현 또는
lodash-es트리쉐이킹.
// shared/i18n/ko.ts
export const ko = {
common: {
save: '저장',
cancel: '취소',
delete: '삭제',
},
session: {
list: {
empty: { title: '첫 모의면접을 시작해보세요', cta: '면접 시작' },
},
},
};Phase 1은 한국어 only. Phase 2 영어 추가 대비해서 키 기반 운영.
// shared/config/env.ts
export const env = {
API_BASE_URL: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080',
SSE_BASE_URL: import.meta.env.VITE_SSE_BASE_URL ?? 'http://localhost:8080',
GITHUB_OAUTH_CLIENT_ID: import.meta.env.VITE_GITHUB_OAUTH_CLIENT_ID ?? '',
} as const;환경변수 접근은 항상 이 wrapper를 통해. 직접 import.meta.env.* 사용 금지 (테스트 어렵고, 누락 검증 못함).