고딕 로리타 패션 커머스 belchic.shop의 클론 프로젝트입니다.
실제 서비스가 아닌 프론트엔드 아키텍처 역량을 보여주기 위한 포트폴리오 데모입니다.
| Category | Tech |
|---|---|
| Framework | Next.js 16 (App Router), React 19, TypeScript |
| Database | PostgreSQL, Prisma (@prisma/adapter-pg custom adapter) |
| Auth | better-auth (email/password) |
| Data Fetching | TanStack Query, @suspensive/react-query, Streaming SSR |
| Styling | Tailwind CSS v4, shadcn/ui (radix-luma), OKLCH color system |
| Payment | Stripe (test mode, webhook) |
| Forms | react-hook-form, Zod (Korean locale) |
| URL State | nuqs |
| Testing | Vitest, @testing-library/react, jsdom |
| Lint/Format | oxlint, oxfmt |
| Package Mgr | pnpm |
| CI/CD | GitHub Actions (format + lint + test parallel) |
Schema → Model → Service → Action → Query → Component
6단계로 관심사를 완전히 분리했습니다. 각 레이어는 명확한 책임을 가지며 의존 방향이 단방향으로 흐릅니다.
| Layer | 역할 | 예시 |
|---|---|---|
| Schema | Zod 런타임 검증 | lib/schemas/product.schema.ts |
| Model | TypeScript 타입 정의 | lib/models/product.model.ts |
| Service | 비즈니스 로직 + Prisma 쿼리 | lib/services/product.service.ts |
| Action | "use server" 래퍼, withAction 적용 |
lib/actions/product.action.ts |
| Query | TanStack Query options | lib/queries/product.query.ts |
| Component | UI 렌더링 (Server / SuspenseQuery) | components/product/ProductCard.tsx |
모든 서버 액션은 withAction() 유틸리티로 래핑되어 ActionResult<T> 타입을 반환합니다.
타입 세이프한 에러 핸들링이 가능하며, wrapQueryFn()을 통해 TanStack Query와 매끄럽게 연결됩니다.
export const getProductsAction = withAction(GetProductsParamsSchema, async (params) =>
getProducts(params),
);장바구니 수량 변경과 아이템 삭제는 TanStack Query의 onMutate로 캐시를 즉시 반영하고,
실패 시 onError에서 롤백 + toast 에러를 표시합니다.
수량 입력은 es-toolkit의 debounce(500ms)로 지연 저장하여 불필요한 요청을 최소화했습니다.
미들웨어가 cart_session_id 쿠키를 발급하여 비회원도 장바구니를 사용할 수 있습니다.
로그인 시 mergeGuestCart 서버 액션이 세션 장바구니를 사용자 장바구니에 자동 병합합니다.
홈페이지의 4개 CollectionSection은 <Suspense>로 감싸져 각각 독립적으로 스트리밍됩니다.
초기 HTML에 HeroBanner가 먼저 도착하고, 컬렉션 데이터는 준비되는 순서대로 점진적으로 렌더링되어 TTFB를 최소화했습니다.
모든 페이지에 JSON-LD 구조화 데이터(schema-dts)를 삽입했습니다:
- 메인 페이지:
Organization+WebSite - 상품 상세:
Product(가격, 재고, 이미지, 상품명) lib/seo.ts에 빌더 함수로 분리하여 재사용
전체 UI는 DESIGN.md에 문서화된 시스템을 따릅니다:
- Minimalist Monochrome: OKLCH 흑백 스케일만 사용, 별도의 accent color 없음
- High-Contrast Interaction: 모든 인터랙션 요소는 테두리, hover, active/selected 상태가 명확
- Korean First: 모든 UI 텍스트, 에러 메시지, 포맷팅이 한국어 (
ko-KR로케일) - Restrained Motion: slide-in-bottom / pulse / spinner 세 가지 애니메이션만 사용
- Server/Client 분리 원칙: UI Primitive는 기본 Server Component, 상태가 필요한 경우만 Client Component
36개 테스트 파일, 3개의 헬퍼 파일로 전 영역을 커버합니다:
- Schemas: Zod 검증 로직 단위 테스트
- Services: Prisma mock으로 비즈니스 로직 검증
- Actions:
withAction래핑 + 권한 검증 통합 테스트 - Components: 사용자 상호작용 시나리오 (@testing-library/user-event)
- API Routes: Stripe webhook 처리 테스트
- Middleware: 쿠키 기반 guest 세션 플로우 검증
GitHub Actions 3병렬 파이프라인:
format:oxfmt실행, 변경사항 자동 커밋lint:oxlint --max-warnings 0test:vitest run
| Route | Description |
|---|---|
/ |
홈 (Hero + 4 streaming sections) |
/products/[handle] |
상품 상세 |
/collections/[collection] |
컬렉션별 상품 목록 |
/search |
검색 |
/cart |
장바구니 |
/favorite |
관심 상품 |
/orders/[id] |
주문 상세 |
/mypage |
마이페이지 |
/sign-in |
로그인 |
/sign-up |
회원가입 |
├── app/ # Next.js App Router pages
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── auth/ # SignInForm, SignUpForm
│ ├── product/ # ProductCard, ProductDetail, ProductContext
│ ├── cart/ # Cart, CartItem
│ ├── collection/ # CollectionProductList
│ ├── search/ # SearchProductList
│ ├── favorite/ # FavoriteGrid
│ ├── order/ # OrderHistory, OrderDetail
│ └── shared/ # ProductListPagination
├── lib/
│ ├── schemas/ # Zod schemas (Korean locale)
│ ├── models/ # TypeScript types
│ ├── services/ # Business logic + Prisma queries
│ ├── actions/ # Server actions (withAction wrapper)
│ ├── queries/ # TanStack Query options
│ └── generated/prisma/ # Generated Prisma client
├── hooks/ # Custom React hooks
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Migration history
├── resources/icons/ # Custom SVG icon components
├── tests/ # Vitest test suites
├── DESIGN.md # UI design system documentation
└── middleware.ts # Next.js middleware (guest session)