UMC 운영팀이 정책·계정·데이터를 한 곳에서 관리할 수 있도록 만든 백오피스입니다. 권한(챌린저, 파트장, 회장단, 총괄)에 따라 다른 뷰를 제공하며, 운영 효율과 정책 반영 속도를 높이는 데 집중합니다.

- Contributors
- Tech Stack
- 시작하기
- 신규 팀원 온보딩
- 스크립트
- 프로젝트 구조
- 라우팅
- 스타일링
- 상태 관리
- 폼 관리
- 코드 품질 도구
- Commit Convention
- Code Convention
- 테스트
- 기여 가이드
- 배포
- 트러블슈팅
| 김연진(코튼) |
|---|
| @yeonjin719 |
| 김연진(코튼) |
|---|
| @yeonjin719 |
| 도구 | 최소 버전 |
|---|---|
| Node.js | >=22.0.0 |
| pnpm | >=9.0.0 |
# 저장소 클론
git clone https://github.com/UMC-PRODUCT/product-web.git
cd product-web
# 의존성 설치
pnpm install프로젝트 루트에 .env 파일을 생성하세요:
touch .envVite 환경 변수는 VITE_ 접두사를 사용합니다:
# API 서버 URL (예시)
VITE_SERVER_API_URL=https://api.example.com/api/v1
# 사이트 기본 URL (프로덕션 필수)
VITE_SITE_URL=https://prod.umc.it.kr
.env파일은.gitignore에 포함되어 있어 Git에 커밋되지 않습니다.
pnpm dev # http://localhost:3000신규 합류자를 위한 상세 가이드는 아래 문서를 참고하세요.
- 프로젝트 구조/레이어 규칙/코드 배치 기준
- 핵심 라이브러리 사용 이유와 실제 적용 위치
- 인증/권한/데이터 흐름
- 품질 게이트(CI/로컬)와 테스트 전략
| 명령어 | 설명 |
|---|---|
pnpm dev |
개발 서버 실행 (port 3000) |
pnpm build |
TypeScript 타입체크 + 프로덕션 빌드 |
pnpm preview |
빌드된 결과물 미리보기 |
pnpm test |
Unit 테스트 실행 (test:unit) |
pnpm test:unit |
Unit/Smoke 테스트 실행 |
pnpm test:browser |
Storybook Browser 테스트 실행 |
pnpm test:ci |
CI 기준 테스트 실행 |
pnpm quality:check |
typecheck + lint + test:ci |
pnpm lint |
ESLint 검사 |
pnpm lint:fix |
ESLint 자동 수정 |
pnpm format |
Prettier로 코드 포맷팅 |
pnpm format:check |
포맷팅 상태 확인 |
pnpm typecheck |
TypeScript 타입 검사만 실행 |
src/
├── app/ # 엔트리/프로바이더/DevTools 등 앱 레벨
│ ├── main.tsx
│ ├── reportWebVitals.ts
│ └── styles.css
├── routes/ # TanStack Router 파일 기반 라우트 (얇은 어댑터)
│ ├── (app)/ # 앱 레이아웃 그룹
│ ├── (auth)/ # 인증 레이아웃 그룹
│ └── __root.tsx # 루트 레이아웃
├── features/ # 기능 단위 모듈
│ ├── auth/ # 로그인/회원가입
│ ├── management/ # 계정/학교/정책/공지/데이터 관리
│ ├── dashboard/
│ ├── apply/
│ ├── recruiting/
│ └── home/
├── shared/ # 전역 공유 자원
│ ├── assets/ # 이미지, 아이콘 등 정적 자산
│ ├── layout/ # 레이아웃 컴포넌트 (Header, Footer)
│ ├── ui/ # 공용 UI (Button, Modal, Tab 등)
│ ├── styles/ # 스타일 시스템 (theme, global, media)
│ ├── types/ # TypeScript 타입 정의
│ ├── utils/ # 유틸리티 함수
│ └── mocks/ # 공유 Mock 데이터
├── routeTree.gen.ts # TanStack Router 자동 생성 트리
└── vite-env.d.ts
@app/*→src/app/*@features/*→src/features/*@shared/*→src/shared/*@routes/*→src/routes/*@/*→src/*(가능하면 위 별칭 우선 사용)
TanStack Router의 파일 기반 라우팅을 사용합니다.
라우트 파일은 페이지 컴포넌트를 @features/*/pages에서 가져오는 얇은 어댑터로 유지합니다.
| 레이아웃 | 설명 |
|---|---|
src/routes/(app)/route.tsx |
글로벌 레이아웃 |
src/routes/(app)/management/route.tsx |
관리 전용 레이아웃 (헤더 없이 Outlet만) |
src/routes/(auth)/auth/_layout.tsx |
인증 레이아웃 |
/management/*경로 →SuperHeader- 그 외 경로 →
ChallengerHeader - Footer는 flex 레이아웃으로 하단 고정
Emotion CSS-in-JS를 사용합니다.
import { theme } from '@shared/styles/theme'
// 색상
theme.colors.primary
theme.colors.gray[500]
// 타이포그래피
theme.typography.heading1
theme.typography.body2import { media } from '@shared/styles/media'
// 미디어 쿼리
${media.down('tablet')} {
// 태블릿 이하
}
${media.up('desktop')} {
// 데스크탑 이상
}컴포넌트별 스타일은 .style.tsx 파일로 분리합니다:
shared/
└── ui/
└── common/
└── Button/
├── Button.tsx
└── Button.style.tsx
스토어는 feature 내부에 둡니다 (예: src/features/auth/store).
// 예시
import { useAuthStore } from '@features/auth/store/authStore'
const { user, login, logout } = useAuthStore()import { useCustomQuery } from '@/shared/hooks/customQuery'
const { data, isLoading } = useCustomQuery(['users'], fetchUsers)개발 환경에서 TanStack Query DevTools와 Router DevTools가 자동으로 활성화됩니다.
react-hook-form + Zod를 사용합니다.
// src/features/auth/schema/register.ts
import { z } from 'zod/v3'
export const registerSchema = z.object({
school: z.string().min(1, '학교를 선택하지 않았습니다.'),
name: z.string().min(1, '양식이 올바르지 않습니다.'),
nickname: z
.string()
.min(1, '양식이 올바르지 않습니다.')
.regex(/^[가-힣]{1,5}$/, '닉네임은 1~5글자의 한글이어야 합니다.'),
email: z.string().email('유효하지 않은 이메일 주소입니다.'),
serviceTerm: z.boolean().refine((val) => val === true, {
message: '서비스 이용 약관에 동의해 주세요.',
}),
privacyTerm: z.boolean().refine((val) => val === true, {
message: '개인정보 처리 방침에 동의해 주세요.',
}),
marketingTerm: z.boolean(),
})import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { registerSchema } from '@features/auth/schema/register'
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(registerSchema),
})ESLint 9 Flat Config를 사용합니다:
@tanstack/eslint-config기반eslint-plugin-react: React 규칙eslint-plugin-react-hooks: Hooks 규칙eslint-plugin-jsx-a11y: 접근성 규칙simple-import-sort로 import 자동 정렬
pnpm lint # 검사
pnpm lint:fix # 자동 수정pnpm quality:check # 로컬에서 CI와 동일한 품질 게이트 실행prettier.config.js 주요 설정:
| 옵션 | 값 |
|---|---|
| 세미콜론 | 없음 |
| 따옴표 | 작은따옴표 |
| 줄 길이 | 100자 |
| 트레일링 콤마 | all |
| 줄바꿈 | LF |
pnpm format # 포맷팅 적용
pnpm format:check # 포맷팅 확인| Hook | 동작 |
|---|---|
| pre-commit | lint-staged 실행 (ESLint + Prettier) |
| commit-msg | commitlint로 커밋 메시지 검증 |
| pre-push | 빌드 실행 (pnpm run build) |
스테이징된 파일에만 린트/포맷 적용:
*.{ts,tsx}(_.gen.ts, _.d.ts 제외) → ESLint --fix + Prettier*.{js,jsx,cjs,mjs}→ ESLint --fix + Prettier*.{json,md,css,html,yaml}→ Prettier
| 타입 | 설명 |
|---|---|
| feat | 기능 추가 |
| fix | 버그 수정 |
| docs | 문서 변경 |
| style | 포맷/UI 변경 |
| refactor | 리팩터링 |
| test | 테스트 추가/수정 |
| chore | 잡일/설정 |
| ci | CI 설정 |
| build | 빌드 설정 |
| perf | 성능 개선 |
<type>(<scope>): <subject>
<body>
<footer>
예시:
feat(auth): 로그인 폼 유효성 검사 추가
- 이메일 형식 검증
- 비밀번호 최소 길이 검증
Closes #123| 구분 | 내용 |
|---|---|
| 브레이킹 | BREAKING CHANGE: 문구로 명시 |
| 포맷/린트 | pnpm lint 준수, 임포트 순서 규칙 준수, 경로는 @shared/*, @features/*, @app/*, @routes/* 우선 사용 |
| 스타일 | Emotion 사용 시 .style.tsx로 분리, theme.colors/typography, media 우선 사용 |
| 타입 | Array<T> 표기, 공용 유틸(resolveTypo 등)로 널 가드 |
| 컴포넌트 | 공용 Header/Modal/Badge 재사용, 반응형은 media.down/up 활용 |
pnpm test # Unit 테스트 실행
pnpm test:unit # Unit/Smoke 테스트 실행
pnpm test:browser # Storybook Browser 테스트 실행
pnpm test:ci # CI 기준 테스트 실행- Vitest: 테스트 러너
- @testing-library/react: React 컴포넌트 테스트
- jsdom: 브라우저 환경 시뮬레이션
pnpm quality:check| 브랜치 | 용도 |
|---|---|
main |
프로덕션 브랜치 |
develop |
개발 브랜치 (PR 기본 대상) |
feature/* |
기능 개발 |
fix/* |
버그 수정 |
chore/* |
설정/잡일 |
refactor/* |
리팩터링 |
-
develop브랜치에서 새 브랜치 생성git checkout develop git pull origin develop git checkout -b feature/기능명
-
작업 후 커밋 (Conventional Commits 준수)
git add . git commit -m "feat: 새로운 기능 추가"
-
PR 생성
.github/pull_request_template.md템플릿 사용- 관련 이슈 연결
- 테스트 결과 첨부
개발 속도/진행률 트래킹을 위해 아래 항목을 필수로 준수합니다.
- 이슈 생성 시 타입을 지정합니다:
feat,fix,refactor,chore,docs,test,perf,design - 모든 이슈는 생성 직후 GitHub Project와 Milestone에 연결합니다.
- 이슈 상태는 Project 기준으로 관리합니다:
Todo -> In Progress -> In Review -> Done - 블로킹/의존성은 이슈 본문에 명시합니다:
Blocked by #123,Blocks #456(없으면없음) - PR 생성 시 PR 타입과 이슈/프로젝트/마일스톤/블로킹 정보를 템플릿에 모두 작성합니다.
- 모든 PR은 Copilot 리뷰를 반드시 받고, Copilot/사람 리뷰 코멘트는 모두 Resolve 처리합니다.
- 상호 리뷰를 원칙으로 하며, 최소 1명 이상 승인 후 머지합니다.
pnpm build # dist/ 폴더 생성빌드된 dist/ 폴더를 정적 호스팅 서비스에 배포합니다.
.github/workflows/amplify-build-status.yml은 GitHub Actions에서 Amplify job 상태를 폴링해 진행 로그와 최종 상태를 GitHub check로 노출합니다.
- 기본적으로
develop,main브랜치에 push될 때만 동작합니다. workflow_dispatch로 수동 실행할 때는develop또는main을 직접 선택할 수 있습니다.vars.AMPLIFY_BRANCH_NAME를 설정하면 현재 Git 브랜치 대신 해당 Amplify 브랜치를 조회합니다.- GitHub 이벤트로 시작된 모니터링만 GitHub에서 보이며, Amplify 콘솔에서 수동으로 다시 돌린 빌드는 별도
workflow_dispatch실행이 필요합니다. - AWS 인증은 OIDC AssumeRole(
AWS_ROLE_TO_ASSUME)만 지원합니다.
필수 Repository Variables / Secrets:
vars.AWS_REGIONvars.AMPLIFY_APP_IDsecrets.AWS_ROLE_TO_ASSUME
선택 Repository Variables:
vars.AMPLIFY_BRANCH_NAME- Amplify에 실제로 연결된 브랜치 이름을 강제로 지정할 때 사용합니다.
- 이 값을 쓰면 현재 Git 커밋 기준 추적 대신 해당 Amplify 브랜치의 최신 job 기준으로 모니터링합니다.
ERROR: This project requires pnpm version >=9.0.0해결:
corepack enable
corepack prepare pnpm@latest --activateERROR: This project requires Node.js version >=22.0.0해결 (nvm 사용 시):
nvm install 22
nvm use 22pnpm build는 타입체크를 포함합니다. 개발 중 타입 문제를 미리 확인하려면:
pnpm typechecksimple-import-sort 플러그인이 import 순서를 검사합니다. 자동 수정:
pnpm lint:fixpnpm prepare # Husky 재설치Private