Ecommerce Template
Next.js·React 등으로 만든 최신 전자상거래 템플릿
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기개인 독서기록 관리부터 도서 정보 검색, 중고 서적 거래 및 도서 리뷰 컨텐츠 공유를 위한 종합 책 플랫폼
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
AI 도서 추천·요약, 키워드 도서 검색, 독서 기록 관리, 에스크로 결제 기반 중고책 거래, 실시간 채팅, 도서 리뷰, 독자 커뮤니티가 결합된 통합 도서 플랫폼. bash 1. 의존성 설치 pnpm install 2. 환경 변수 준비 (루트에 .env 생성) cp .env.example .envREADME 전문 보기 ↗
책과 지식의 선순환 플랫폼
키워드 도서 검색, AI 도서 요약, 독서 기록 관리, 에스크로 결제 기반 중고책 거래, 실시간 채팅, 도서 리뷰, 독자 커뮤니티가 결합된 통합 도서 플랫폼.
| 서비스 | https://bookjeok.com |
| 구성 | Turborepo 모노레포 — 앱 3개(web / server / admin), 공용 패키지 3개(core / api-client / react-query), 운영자 도구 1개(tools/book-ingest) |
| 프론트엔드 | Next.js 15 App Router + React 19, 한국어/영어 다국어, ISR + On-Demand Revalidation |
| 백엔드 | NestJS 11 + TypeORM + PostgreSQL(pgvector, cube/earthdistance) |
| AI | Google Gemini — 도서 요약. RAG 대화형 추천(의도 분류 → pgvector 검색 → 리랭킹)은 서버에 유지하되 웹 UI 비노출 |
| 실시간 | Socket.IO 채팅·알림 게이트웨이 |
| 결제 | 토스페이먼츠 에스크로 + Delivery Tracker 배송 추적 + 스케줄러 기반 자동 확정/환불 |
| 인프라 | Vercel(web) · Azure Container Apps(server) · Supabase PostgreSQL |
┌─────────────────────────────┐
│ Browser / Mobile Web │
└──────────────┬──────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ HTTPS (REST) │ SSE │ WebSocket
▼ ▼ ▼
┌────────────────────────────────────────────────────────────────────────┐
│ apps/web — Next.js 15 (Vercel) │
│ App Router · RSC/ISR · next-intl(ko/en) · TanStack Query · Zustand │
│ Route Handlers: /api/upload, /api/revalidate, /rss.xml, sitemap.ts │
└───────────────────────────────┬────────────────────────────────────────┘
│
@bookjeok/react-query → api-client → core
│
▼
┌────────────────────────────────────────────────────────────────────────┐
│ apps/server — NestJS 11 (Azure Container Apps, Docker) │
│ │
│ Global Pipeline │
│ cookie-parser → helmet → compression → CORS │
│ ThrottlerGuard → LoggingInterceptor → SmartCacheInterceptor │
│ → ActivityTrackingInterceptor → CacheInvalidationInterceptor │
│ → TransformInterceptor → ClassSerializerInterceptor │
│ → ValidationPipe(whitelist) → GlobalExceptionFilter(ERROR_CODES) │
│ │
│ Domain Modules │
│ auth · user · book · review · comment · reading-log · wishlist │
│ used-book-sale · order · trade · chat · llm · search · search-keyword │
│ insights · notification · feedback · health │
│ │
│ Cross-cutting │
│ CLS Transactional · EventEmitter · @nestjs/schedule · Resend Mail │
│ SmartCache · Activity Log · Idempotency │
└───┬──────────────┬───────────────┬────────────────┬────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────────────────────┐
│ Supabase │ │ Gemini │ │ Toss │ │ Naver · Kakao (OAuth) │
│ Postgres │ │ Flash / │ │ Payments │ │ Delivery Tracker │
│ pgvector │ │ embedding │ │ (Escrow) │ │ Resend │
│ earthdist│ │ │ │ │ │ Vercel Blob │
└──────────┘ └────────────┘ └──────────┘ └────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ apps/admin — Next.js 15 관리자 포털 │
│ 초기 세팅만 된 미배포 앱 (통계·검수·캐시 제어 화면 뼈대) │
└────────────────────────────────────────────────────────────────────────┘
그림의 SSE 경로와 Gemini 임베딩은 RAG 대화형 추천용입니다. 추천은 2026-09-29에 웹 UI에서 내렸으므로 지금은 브라우저가 이 경로를 쓰지 않습니다(서버 엔드포인트는 유지).
자체 DB에 적재한 도서 약 6.6만 종을 제목·저자·출판사로 검색합니다. pg_trgm GIN 인덱스 기반 부분일치이며, 관련도(완전일치 → 접두일치 → 부분일치)로 버킷을 나눈 뒤 알라딘 판매지수(salesPoint)로 순서를 가립니다. 런타임에 외부 도서 API를 호출하지 않습니다(아래 「외부 서비스 연동」 참고). TanStack Query 기반 무한 스크롤로 결과를 점진적으로 탐색하며, 사용자 검색어는 정규화 후 별도로 기록되어(초성 제거·공백 정리·2글자 미만 폐기) 최근 1년 기준 인기 검색어 Top 10을 집계합니다.
2026-09-29부터 웹에 노출하지 않습니다. 검색 화면의 AI 추천 탭을 내렸고
?mode=ai도 키워드 검색으로 보입니다. 서버 모듈·엔드포인트(/search/ai,/search/ai/stream)와 웹 컴포넌트는 되살릴 때를 위해 남겨 두었습니다. 비용과 Supabase 무료 티어 용량 때문에books.embedding컬럼은 유지하되 값은 비워 두었으므로, 다시 켜려면 임베딩 재생성이 먼저입니다. 아래는 구현된 설계입니다.
사용자가 "주말에 가볍게 읽을 만한 에세이 찾고 있어요" 같은 자연어를 입력하면, 도서 DB에서 의미론적 유사도를 기반으로 도서를 찾아 추천합니다.
도서 메타데이터(제목·저자·줄거리·장르)를 결합한 텍스트를 Google gemini-embedding-001 모델로 768차원 벡터로 변환하여 PostgreSQL pgvector에 저장하는 사전 임베딩 작업을 거칩니다. 검색 시점에는 사용자의 질문도 동일한 모델로 임베딩한 뒤, 코사인 유사도 기반 벡터 검색으로 후보 도서를 추출합니다.
search_books)를 등록하여, 사용자의 발화가 실제 도서 검색 요청인지 단순 대화인지를 판단합니다. 단순 인사나 모호한 표현에는 꼬리 질문으로 대화를 이어가고, 구체적 독서 의도가 감지될 때만 검색 쿼리를 생성합니다.match_books RPC 함수를 호출하여 코사인 유사도 상위 후보 도서를 추출합니다. 유사도 임계값(AI_SIMILARITY_THRESHOLD, 기본 0.35) 미만은 사전 필터링하고, 후보 풀 크기는 AI_CANDIDATE_POOL_SIZE로 조정합니다.reason)를 JSON 구조로 생성하여 반환합니다.사용자 메시지
│
▼
┌──────────────────────────────────────────────┐
│ 1차 LLM Agent — 의도 분류 │
│ Gemini Flash + Function Calling │
│ search_books 도구 호출 여부 판단 │
└──────────────────┬───────────────────────────┘
│ searchQuery 생성
▼
┌──────────────────────────────────────────────┐
│ EmbeddingService │
│ gemini-embedding-001 → 768차원 벡터 생성 │
│ L2 정규화 │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ pgvector 코사인 유사도 검색 │
│ match_books RPC (상위 후보 추출) │
│ 유사도 임계값 사전 필터링 + 중복 도서 제거 │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ 2차 LLM — RAG 합성 & 리랭킹 │
│ 대화 맥락 + 후보 도서 → 최종 추천 엄선 │
│ 추천 서두 메시지 + 책별 개별 reason 생성 │
└──────────────────┬───────────────────────────┘
│
▼
SSE 스트리밍 (POST /search/ai/stream)
응답을 조각 단위로 브라우저에 즉시 전달
결과는 POST /search/ai(일괄 응답)와 POST /search/ai/stream(SSE 스트리밍) 두 가지로 제공됩니다. 웹의 커스텀 sse-chat-client가 스트림을 파싱해 챗 UI에 점진적으로 렌더링하도록 구현돼 있으나, 현재 그 챗 UI는 화면에 연결돼 있지 않습니다.
도서 상세 페이지에서 요청하면 서버가 ISBN으로 DB의 제목·저자·소개·출판사를 읽어 Gemini에 전달하고, 단순 소개글과 차별화된 분석적 요약을 생성합니다. 요청 본문의 서지는 저장할 요약의 기준으로 쓰지 않습니다.
생성된 요약은 ISBN 기준으로 ai_book_summaries 테이블에 캐싱하여 동일 도서에 대한 반복 API 호출을 방지합니다. 모든 AI 요청은 토큰 사용량, 응답 지연시간, 성공/실패 여부를 ai_request_logs 테이블에 별도로 기록합니다.
카카오 맵 API 및 지오코딩을 활용한 위치 기반 중고책 거래 플랫폼입니다. 시/도, 시/군/구 단위 필터링과 지도 기반 거래 장소 확인, PostgreSQL cube/earthdistance 확장과 GiST 인덱스를 이용한 반경·거리순 정렬을 지원합니다.
판매글은 거래 방식을 DIRECT_ONLY(직거래) / DELIVERY_ONLY(택배) / BOTH로 선택할 수 있고, 택배 거래에는 토스페이먼츠 에스크로 결제가 연결됩니다.
결제는 현재 운영에서 꺼져 있습니다. PG 심사 전이라
FEATURE_PAYMENT_ENABLED플래그 뒤에 봉인돼 있고,orders테이블은 0행입니다. 아래 상태 머신과 스케줄러는 구현돼 있으나 동작하지 않는 상태입니다. 직거래는 결제 없이 정상 동작합니다. 플래그를 켜기 전에 처리할 항목이 docs/book-data-migration-plan.md 9-c 「결제 활성화 전 점검 항목」에 있습니다.
판매자가 채팅방에서 구매자 선택
│
▼
AWAITING_PAYMENT ──(24h 미결제)──▶ CANCELLED
│ 구매자 결제 승인
▼
PAID ──(3일 미배송 자동 환불)──▶ CANCELLED
│ 판매자 운송장 등록
▼
SHIPPED ──(Delivery Tracker 폴링)──▶ DELIVERED
│
구매자 확정 / 2일 후 자동 확정
▼
CONFIRMED
▲
구매확정 거부 → DISPUTED ──────┘ (7일 미해결 시 자동 환불)
@VersionColumn 낙관적 잠금 + x-idempotency-key 멱등성 인터셉터FEATURE_PAYMENT_ENABLED와 PaymentFeatureGuard로 PG 심사 전에도 결제 경로만 차단한 채 안전 배포EmailVerifiedGuard로 판매글 작성·거래 채팅·구매자 지정·결제를 인증 회원으로 제한TradeCompletion) 1건당 구매자·판매자가 각각 한 건씩 남기는 양방향 TradeReview, 프로필에 "거래 완료 N건 · 긍정 후기 N%" 신뢰 지표 노출상세 설계는 docs/used-book-pay-implementation.md를 참고하세요.
Socket.IO 게이트웨이 2종(채팅 / 알림)을 운영합니다.
lastReadMessageId) 기반 안 읽은 개수 계산, 핸드셰이크 단계 JWT 검증(authenticateSocket)@bookjeok/react-query의 invalidateTradeCaches를 함께 사용합니다. 거래 완료·후기 작성 자격까지 갱신해 상대방의 열린 채팅방에도 후기 진입점이 반영됩니다.EventEmitter 이벤트만 발행하고, 리스너가 알림 생성·채팅 시스템 메시지·메일 발송을 비동기로 처리합니다.NotificationType·종류별 필수 metadata를 서버 생성 입구에서 검사하고, 웹 등록부가 18종의 문구·이동 경로·시스템 표시를 집중 관리합니다. 타입 검사와 전 종류·한영 계약 테스트로 누락을 검증하며 기존 DB enum·저장·실시간 전달 동작은 유지합니다.chat.room_created 이벤트 → ChatMailListener, async: true). 사용자 문의·제보가 접수되면 운영자(FEEDBACK_NOTIFY_EMAIL)에게도 보냅니다(feedback.created → FeedbackNotifyListener). 도메인별 발송 정의와 공통 렌더링·수신 정책·전달 결과를 사용하며, 인증 재발송 실패는 503으로 전달합니다(가입·이메일 변경·이벤트 발송은 비동기 유지).sanitize-html 기반 XSS 방어/reading-log(예시 달력)book_dimensions). 기록으로 사물·몸 부위·내 키를 넘으면 그 책이 쌓이는 장면을 띄우고, 넘은 사물은 한 해 단위 사물 도감에 그 책의 표지색으로 모임. 출시 후 30일간 접속 시 한 번 넘겨 보는 소개 모달로 알림(announcement), 검색 유입용 공개 소개 페이지 /reading-height/lounge에서 북적 책동산(공개 독서 기록 전체를 한 산으로 쌓은 높이와 다음 이정표), 다른 독자들의 실시간 독서 기록, 인기 도서, 활동 중인 독자, 같은 책을 읽는 독자를 조회/insights에서 지역별 중고 거래 분포, 가격대 분포, 인기 태그 순위, 리액션 비율 등 서비스 전체의 독서·거래 데이터를 ApexCharts로 시각화합니다.
next-intl 기반 [locale] 라우팅sitemap.ts, robots.ts, manifest.ts, RSS 피드(/rss.xml), JSON-LD 구조화 데이터(독서 키재기 소개의 FAQ 포함), canonical/hreflang, 마켓·리뷰·라운지·독서 키재기 전용 정적 공유 카드. 로그인 뒤에 있는 독서 기록과 독서 키재기는 공개 소개 페이지 /reading-log·/reading-height로 검색에 노출(FAQ JSON-LD 포함)| 항목 | 기술 |
|---|---|
| 빌드 오케스트레이션 | Turborepo 2.7 (태스크 그래프 + 캐시) |
| 패키지 매니저 | pnpm 10 Workspaces (overrides로 React 19 / axios / TanStack Query 버전 단일화) |
| 언어 | TypeScript 5 (tsconfig.base.json 공유) |
| 린트·포맷 | ESLint 9 Flat Config, Prettier 3, eslint-plugin-simple-import-sort |
| 런타임 | Node.js 22.x |
apps/web| 항목 | 기술 |
|---|---|
| 프레임워크 | Next.js 15 (App Router, RSC, SSG/ISR, Route Handlers, Server Actions) · React 19 |
| 서버 상태 | TanStack Query v5 + @lukemorales/query-key-factory (쿼리 키 팩토리) |
| 클라이언트 상태 | Zustand v5 (음악 플레이어, 확인 모달, 최근 본 책 등) |
| 스타일링 | Tailwind CSS v4(@tailwindcss/postcss), tailwind-merge, class-variance-authority, tw-animate-css, @tailwindcss/typography |
| UI 컴포넌트 | Radix UI (shadcn/ui 패턴) + Lucide React |
| 애니메이션 | Framer Motion / motion, GSAP, Swiper |
| 에디터 | Tiptap 3 (StarterKit, Image + resize, Link, Highlight, TextAlign, Color, Underline, Placeholder, BubbleMenu) |
| 폼 & 검증 | React Hook Form + Zod 4 + @hookform/resolvers |
| 차트 | ApexCharts / react-apexcharts |
| 지도·주소 | react-kakao-maps-sdk, react-daum-postcode |
| 결제 | @tosspayments/tosspayments-sdk v2 |
| 실시간 | socket.io-client, 커스텀 SSE 클라이언트 |
| 다국어 | next-intl 4 (ko/en) |
| 테마 | next-themes (다크 모드) |
| 이미지 | browser-image-compression, @vercel/blob |
| 마크다운·정제 | react-markdown + remark-gfm, sanitize-html |
| 토스트·UX | sonner, react-intersection-observer |
| 계측 | @vercel/analytics, @vercel/speed-insights, GA4, Microsoft Clarity, AdSense |
| 테스트·문서 | Vitest 4 + Testing Library + jsdom, Storybook 8, @next/bundle-analyzer |
apps/server| 항목 | 기술 |
|---|---|
| 프레임워크 | NestJS 11 (Express 플랫폼) |
| ORM / DB | TypeORM 0.3 · PostgreSQL(pgvector, cube, earthdistance) · pg |
| 트랜잭션 | nestjs-cls + @nestjs-cls/transactional (TypeORM 어댑터) — 선언적 CLS 트랜잭션 |
| 인증 | Passport (JWT / JWT-Refresh / Naver / Kakao), @nestjs/jwt, bcrypt |
| 실시간 | @nestjs/websockets + @nestjs/platform-socket.io, Socket.IO 4 |
| AI | @google/generative-ai (Gemini Flash, gemini-embedding-001) |
| 캐싱 | @nestjs/cache-manager + cache-manager 7 위에 자체 SmartCache 레이어 |
| 스케줄링 | @nestjs/schedule (Cron) |
| 이벤트 | @nestjs/event-emitter (도메인 이벤트 · 탈퇴 정리 · 알림 팬아웃) |
| 검증 | class-validator, class-transformer, 전역 ValidationPipe(whitelist) |
| 보안 | helmet, @nestjs/throttler, cookie-parser, CORS 오리진 화이트리스트 |
| 성능 | compression, TypeORM 커넥션 풀(max 40) |
| 외부 통신 | @nestjs/axios (토스페이먼츠, 배송 추적) |
| 메일 | Resend |
| 스토리지 | @vercel/blob |
| API 문서 | @nestjs/swagger — /api에서 OpenAPI 문서 제공 |
| 헬스체크 | @nestjs/terminus — GET /health |
| 테스트 | Jest 29 + ts-jest, Supertest |
| 항목 | 기술 |
|---|---|
| 프론트엔드 호스팅 | Vercel (ISR + On-Demand Revalidation) |
| 백엔드 호스팅 | Azure Container Apps + Azure Container Registry (Docker) |
| 데이터베이스 | Supabase PostgreSQL |
| CI | GitHub Actions (lint → test → build), GitLab CI (Docker 이미지 빌드/푸시) |
| CD | GitHub Actions → ACR 이미지 푸시 → Container Apps 배포 |
| 로컬 개발 | Docker Compose (PostgreSQL + pgvector) |
새 기능은 항상 packages/core의 인터페이스·API_PATHS·쿼리 키부터 정의하고, 서버 DTO가 그 인터페이스를 implements합니다. 프론트와 백엔드가 같은 타입 정의를 컴파일 타임에 공유하므로 API 계약이 깨지면 빌드가 실패합니다. 의존성은 core → api-client → react-query → web/admin 단방향이며, 역방향 import와 앱 간 직접 import를 금지합니다.
@SmartCache({ prefix, ttl, keyStrategy }) 데코레이터로 응답을 캐싱하고, @InvalidateCache(prefix)로 쓰기 시점에 해당 프리픽스의 키를 한 번에 제거합니다. SmartCacheStore가 prefix→key 집합을 인메모리로 관리해 cache-manager가 제공하지 않는 "프리픽스 단위 삭제"를 구현했습니다. keyStrategy로 ip / user / ip+user / global 스코프를 선택합니다.
x-idempotency-key 헤더가 있는 요청은 캐시에 processing 락을 걸어 처리 중인 중복 요청을 409로 차단하고, 완료된 요청은 최초 응답을 재생합니다. 같은 키의 동시 요청도 핸들러를 한 번만 실행합니다(단일 인스턴스 기준). 결제처럼 재시도가 곧 이중 과금이 되는 경로와 댓글·리뷰·판매글·독서기록·직거래 등록을 방어합니다.
ERROR_CODES에 도메인별 코드(AUTH_xxx, SALE_xxx, ORDER_xxx …)와 메시지를 등록하고 서비스는 BusinessException만 던집니다. GlobalExceptionFilter가 모든 예외를 동일한 JSON 형태로 변환하고 TransformInterceptor가 성공 응답 봉투를 통일해, 프론트는 단일 에러 핸들러로 대응합니다.
탈퇴 시 user.withdrawn 이벤트 하나만 발행하면 chat · comment · feedback · llm · notification · reading-log · review · used-book-sale · user · activity 10개 리스너가 각자의 데이터를 정리합니다. 도메인 모듈 간 직접 의존 없이 정리 로직을 확장할 수 있습니다.
이벤트 이름·payload는 서버의 각 features/*/events/가 소유하고 발행자와 리스너가 같은 계약을
사용합니다. emitDomainEvent·emitDomainEventAsync·@OnDomainEvent가 필수 payload와 구독 타입을
검사하며 주문의 커밋 후 대기 이벤트도 이름별 payload를 연결합니다. 현재 26개 이름·리스너 옵션·오류
전파를 계약 테스트로 확인합니다. 탈퇴는 트랜잭션 안에서 emitAsync를 기다리고 거래 상태 이벤트는
기존 커밋 후 순서를 유지합니다. 상세는 서버 공용 문서에 있습니다.
@nestjs-cls/transactional로 QueryRunner를 서비스 시그니처에 끌고 다니지 않고 트랜잭션을 전파합니다. 채팅방 + 참가자 생성, 주문 생성 + 판매 상태 변경, 거래 완료 기록, 리뷰 본문 + 태그 + 카운터, 회원 탈퇴 정리처럼 복수 엔티티가 얽힌 작업에 적용됩니다.
@TrackActivity(type)가 붙은 엔드포인트 호출을 ActivityTrackingInterceptor가 가로채 activity_logs에 비동기 적재합니다. 로그인·검색·조회·작성 등 30여 개 지점이 계측되어 있으며, 현재는 감사(audit) 로그 용도로 적재만 하고 조회하는 기능은 없습니다.
전체 GIS 스택이 필요하지 않아 PostgreSQL 기본 확장인 cube/earthdistance만 활성화하고, ll_to_earth(latitude, longitude) GiST 인덱스를 모듈 부팅 시 CREATE INDEX IF NOT EXISTS로 보장합니다. 반경 필터(radius)와 거리순 정렬을 인덱스로 처리합니다.
판매글 검색은 offset이 아닌 Base64 커서를 사용해, 목록이 실시간으로 바뀌어도 중복·누락 없이 무한 스크롤됩니다.
채팅 읽음 처리를 메시지별 read_receipts 테이블에서 참가자별 lastReadMessageId 워터마크 방식으로 전환해, 읽음 레코드가 메시지 수에 비례해 증가하던 구조를 제거했습니다. 적용 이력은 docs/manual-ddl-log.md에 있습니다.
목록·상세 페이지를 ISR로 정적 서빙합니다. 판매글 수정·거래 상태 변경, 리뷰 수정, 회원 탈퇴처럼 상세 HTML이 바로 틀려지는 쓰기는 웹 뮤테이션이 서버 액션(shared/actions/revalidate.ts)으로 해당 경로를 즉시 비우고, 목록·집계는 시간 기반 revalidate에 맡깁니다. 시크릿 토큰 기반 웹훅(/api/revalidate)도 있으나 호출하는 관리자 포털이 미배포라 지금은 쓰이지 않습니다. 규칙은 apps/web/docs/CACHING.md에 있습니다.
FEATURE_PAYMENT_ENABLED / NEXT_PUBLIC_FEATURE_PAYMENT_ENABLED와 PaymentFeatureGuard로, PG 심사 완료 전에도 결제 코드를 운영에 배포한 채 진입만 차단합니다.
| 서비스 | 용도 | 사용 위치 |
|---|---|---|
| 네이버 / 카카오 OAuth | 소셜 로그인 | server: auth (Passport 전략) |
| 네이버 IndexNow | 공개 리뷰 변경 URL 알림 (운영 플래그 활성화 시, 색인 보장 없음) | server: review, web: /api/revalidate |
| Google Gemini | 도서 요약. RAG 추천의 의도 분류·합성·임베딩(웹 UI 비노출) | server: llm, search |
| 토스페이먼츠 | 에스크로 결제 승인·취소·웹훅 | server: order, web: order |
| Delivery Tracker | 택배 배송 상태 조회 및 30분 주기 폴링 | server: order |
| Resend | 인증 링크·채팅 개설 알림·운영자 문의 알림 메일 | server: shared/mail |
| Vercel Blob | 리뷰·판매글·프로필 이미지 업로드/삭제 | web: /api/upload, server |
| Vercel Image Optimization | 홈 메인 슬라이더의 R2 표지를 화면 크기에 맞게 변환·캐시 | web: book-slider |
| 카카오 맵 SDK | 거래 위치 지도, 지오코딩 | web: shared/components/map |
| 다음 우편번호 | 배송지 주소 입력 | web: order/address-input |
| GA4 · Microsoft Clarity · AdSense | 트래픽 분석, 행동 분석, 광고 | web: shared/components/analytics, ads |
| 카카오 책 검색 API | 신간 적재 (운영자 도구 전용, 런타임 경로 아님) | tools/book-ingest |
| 알라딘 Open API | 신간 적재 (운영자 도구 전용, 2026-10-30 종료 시 제거) | tools/book-ingest |
| Cloudflare R2 | 도서 표지 원본 저장·서빙 (cdn.bookjeok.com) | tools/book-ingest(업로드), web(표시) |
도서 데이터는 런타임에 외부 API를 쓰지 않습니다. 과거에는 네이버 도서 API와 알라딘 Open API를 연동했으나, 알라딘 종료(2026-10-30)에 대비해 2026-09-08에 공급처 체인에서 제거했습니다. 지금은 검색·상세 모두 자체 DB 단독이며, 외부 공급처를 런타임 경로에 두지 않는 것이 방침입니다. 신규 도서는 서버가 아니라 운영자가 필요할 때 로컬에서 돌리는 적재 도구(
tools/book-ingest, 카카오 책 검색 API를 입구로 사용, 10/30까지는 알라딘도 선택 가능)로 확보합니다. 표지도 2026-09-09 컷오버로 Cloudflare R2(cdn.bookjeok.com)에서 나갑니다. 홈 메인 슬라이더는 Vercel 이미지 최적화를 거쳐 화면 크기에 맞는 표지를 받습니다. 경위와 남은 정리 항목은 docs/book-data-migration-plan.md에 있습니다.
Monorepo packages/ 디렉토리에 도메인 모델과 통신 클라이언트를 집약하여 플랫폼 전반의 코드 재사용성을 극대화했습니다.
| Package | Role |
|---|---|
@bookjeok/core | 데이터 모델 인터페이스, 서버와 공유하는 enum·입력 제한·에러 코드, API 경로 상수(API_PATHS), 포맷터, 쿼리 키 팩토리 (브라우저·Node 전용 API 없음) |
@bookjeok/api-client | 빈 Axios 인스턴스(publicApiClient, privateApiClient)와 API 호출 모듈. baseURL·응답 봉투·토큰 갱신 인터셉터는 웹(shared/libs/axios.ts)이 붙임 |
@bookjeok/react-query | TanStack Query 쿼리/뮤테이션 훅과 캐시 무효화 규칙 |
bookjeok/
├── apps/
│ ├── web/ # Next.js 15 사용자 웹 프론트엔드
│ │ ├── src/app/ # App Router ([locale] 다국어, route handlers, sitemap/robots/rss)
│ │ ├── src/views/ # 페이지 뷰 조립 레이어
│ │ ├── src/features/ # 도메인 기능 (announcement, auth, book, book-sale, chat, comment,
│ │ │ # confirm, feedback, insights, intro, music, notification,
│ │ │ # order, reading-log, review, trade, user)
│ │ ├── src/shared/ # 공용 컴포넌트·프로바이더·훅·유틸·i18n·analytics
│ │ ├── src/layouts/ # DefaultLayout, Header, Navigation, BottomDock
│ │ ├── docs/ARCHITECTURE.md # 컴포넌트 구조 & i18n 가이드
│ │ └── docs/CACHING.md # ISR · 쿼리 캐시 구조와 재검증 규칙
│ │
│ ├── admin/ # Next.js 15 관리자 포털 (초기 세팅만 된 미배포 앱)
│ │ ├── src/app/dashboard/ # 운영 통계, 매물/리뷰 검수, ISR 캐시 제어
│ │ └── src/stores/ # 관리자 인증 상태
│ │
│ └── server/ # NestJS 11 백엔드 API
│ ├── src/app/ # 루트 모듈 (TypeORM, CLS, Throttler, Cache, Schedule)
│ ├── src/features/ # 18개 도메인 모듈
│ │ ├── auth/ user/ book/ review/ comment/ reading-log/ wishlist/
│ │ ├── used-book-sale/ order/ trade/ chat/ notification/
│ │ ├── llm/ search/ search-keyword/
│ │ └── insights/ feedback/ health/
│ └── src/shared/ # 횡단 관심사
│ ├── activity/ # 활동 로그 (데코레이터 + 인터셉터)
│ ├── cache/ # SmartCache (캐싱/무효화 데코레이터)
│ ├── exceptions/ # BusinessException, ERROR_CODES
│ ├── filters/ # GlobalExceptionFilter
│ ├── interceptors/ # Transform, Logging, Idempotency, ViewCount
│ └── mail/ # 공통 메일 렌더링·수신 정책·Resend 전달
│
├── packages/
│ ├── core/ # @bookjeok/core
│ ├── api-client/ # @bookjeok/api-client
│ └── react-query/ # @bookjeok/react-query
│
├── tools/
│ └── book-ingest/ # 신간 적재 운영자 도구 (로컬 실행, 배포 안 함)
│
├── docs/ # 운영·설계 문서
│ ├── used-book-pay-implementation.md
│ ├── book-data-migration-plan.md
│ ├── manual-ddl-log.md
│ ├── seo-audit-2026-09-19.md
│ ├── review-search-audit-2026-10-07.md
│ ├── review-search-official-guidance-2026-10-07.md
│ ├── vercel-usage-audit-2026-09-20.md
│ └── ddl/ # 운영에 적용한 DDL 원본
├── .agents/rules/ # 코드베이스 컨벤션 (개발자 & AI 에이전트 공용)
├── .github/workflows/ # CI, Azure Container Apps 배포
├── docker/postgres/init/ # 로컬 DB 초기화 스크립트 (확장 설치)
├── docker-compose.yml # 로컬 PostgreSQL + pgvector
├── turbo.json
└── pnpm-workspace.yaml
POST /auth/exchange로 안전하게 교환tokenVersion 기반 즉시 무효화 — 로그아웃 또는 비정상 세션 감지 시 user.tokenVersion을 증가시켜 Refresh Token을 즉시 무효화EmailVerifiedGuard로 중고거래 진입 경로를 인증 회원으로 제한(사기·어뷰징 방어)shared/websocket/authenticate-socket.ts가 WebSocket 핸드셰이크 단계에서 JWT를 검증하고 탈퇴 계정을 차단. 방 참여(joinRooms)는 별도로 참여자 여부를 확인ValidationPipe({ whitelist, forbidNonWhitelisted })로 정의되지 않은 필드 차단sanitize-html로 정제 후 렌더링helmet, CORS 오리진 화이트리스트, credentials: true 쿠키 정책| 항목 | 현황 |
|---|---|
| 서버 단위 테스트 | Jest — 55개 spec (주문 서비스·스케줄러·토스 연동·채팅 게이트웨이·가드·도메인 이벤트 계약 등) |
| 웹 테스트 | Vitest 4 + Testing Library — 120개 테스트 파일 (결제 플로우, 주문 상세, 배송/분쟁 모달, 거래 후기 등) |
| 컴포넌트 문서 | Storybook 8 — 40개 스토리 파일 (사이드바는 src 폴더 구조를 따름) |
| 타입 안전성 | tsc --noEmit 게이트 (server / web / admin) |
| 정적 분석 | ESLint 9 Flat Config + Prettier. 패키지 의존 방향·서버 alias import·BusinessException·웹 PATHS 사용을 규칙으로 검사 (.agents/rules/04-checklist.md) |
| CI | GitHub Actions에서 pnpm turbo lint test → pnpm turbo build. lint는 수정 없이 검사만(고칠 때는 pnpm lint:fix) |
pnpm lint
pnpm test
pnpm build
| 대상 | 방식 |
|---|---|
| apps/web | Vercel — ISR 정적 재검증 + /api/revalidate 온디맨드 갱신 |
| apps/server | GitHub Actions가 같은 커밋에서 서버와 의존 패키지(core)를 lint·test·build로 검증한 뒤(verify job), apps/server/Dockerfile을 모노레포 루트 컨텍스트로 빌드 → Azure Container Registry 푸시 → Azure Container Apps 배포. main/develop push 중 서버 이미지 입력(apps/server·packages/core·공용 tsconfig/tsup/turbo 설정·lockfile)이 바뀐 경우 또는 수동 트리거 |
| DB | Supabase PostgreSQL. 운영은 synchronize: false이며 마이그레이션 도구 없이 DDL을 수동 적용합니다 — 반드시 docs/manual-ddl-log.md에 기록 |
| 컨테이너 이미지 | GitLab CI에서도 web/server 이미지를 빌드해 GitLab Container Registry에 푸시 |
서버는 단일 인스턴스 전제로 캐시·멱등성 키·채팅방 생성 합치기·Socket.IO room·이벤트를 프로세스 메모리에 둡니다. replica를 늘리기 전에 서버 공용 문서의 「다중 인스턴스 전제」를 확인하세요.
서버는 Azure 환경에서 Supabase IPv6
ENETUNREACH를 피하기 위해main.ts에서dns.setDefaultResultOrder("ipv4first")를 강제합니다.
# 1. 의존성 설치
pnpm install
# 2. 환경 변수 준비 (루트에 .env 생성)
cp .env.example .env
# 3. 로컬 DB 기동 (PostgreSQL + pgvector)
pnpm db:up
# 4. 공용 패키지 빌드 (의존 순서 준수)
pnpm --filter @bookjeok/core build
pnpm --filter @bookjeok/api-client build
pnpm --filter @bookjeok/react-query build
# 5. 개발 서버 실행
pnpm dev:web # 웹 + 서버 + 웹·서버가 의존하는 공용 패키지 watch (웹 http://localhost:3000)
pnpm dev:server # 서버 + core watch (http://localhost:8000)
pnpm dev # 전체 워크스페이스
# dev:web·dev:server는 turbo의 `패키지...` 필터로 의존 패키지를 함께 띄웁니다.
# web은 api-client·react-query를 소스가 아니라 dist로 소비하므로 이 watch가 있어야
# 공용 API·훅 수정이 재시작 없이 반영됩니다. 4번의 초기 빌드는 첫 실행 때 dist가
# 아직 없어 웹이 먼저 실패하는 것을 막습니다.
# 6. 부가 도구
pnpm storybook # Storybook (http://localhost:6006)
pnpm ingest serve # 신간 적재 도구 화면 (http://127.0.0.1:4700, 운영 DB 연결 — tools/book-ingest 참고)
pnpm db:logs # DB 로그
pnpm db:down # DB 중지
API 문서는 서버 기동 후 http://localhost:8000/api (Swagger UI)에서 확인합니다.
pnpm --filter @bookjeok/server exec tsc --noEmit
pnpm --filter @bookjeok/web exec tsc --noEmit
pnpm --filter @bookjeok/admin exec tsc --noEmit
pnpm test
전체 목록과 설명은 .env.example에 있습니다. 주요 항목:
| 변수 | 필수 | 설명 |
|---|---|---|
DATABASE_URL | ✅ | PostgreSQL 연결 문자열 |
JWT_SECRET / JWT_REFRESH_SECRET | ✅ | 액세스/리프레시 토큰 서명 키 |
CLIENT_DOMAIN | ✅ | CORS 및 소셜 로그인 리다이렉트 대상 |
NAVER_CLIENT_ID / _SECRET / _CALLBACK_URL | ✅ | 네이버 소셜 로그인 (도서 검색에는 쓰지 않음) |
KAKAO_CLIENT_ID / _SECRET / _CALLBACK_URL | ✅ | 카카오 로그인 |
ALADIN_TTB_KEY | 서버는 쓰지 않음. 적재 도구의 알라딘 공급처 전용(10/30까지) — 지우지 말 것 | |
GEMINI_API_KEY | ✅ | Google Gemini |
GEMINI_MODEL_NAME | AI 도서 추천(search, 웹 UI 비노출)의 Gemini 모델명. 기본 gemini-3.1-flash-lite, AI 요약(llm)은 상수 사용 | |
AI_SIMILARITY_THRESHOLD / AI_CANDIDATE_POOL_SIZE | RAG 벡터 검색 튜닝 (기본 0.35 / 30, 웹 UI 비노출) | |
RESEND_API_KEY / RESEND_FROM_EMAIL | ✅ | 이메일 인증·알림 발송 |
FEEDBACK_NOTIFY_EMAIL | 사용자 문의·제보 알림을 받을 운영자 메일. 비우면 DB에만 쌓임 | |
BLOB_READ_WRITE_TOKEN | ✅ | Vercel Blob 이미지 업로드 |
TOSS_PAYMENTS_SECRET_KEY / _CLIENT_KEY | 토스페이먼츠 에스크로 | |
DELIVERY_TRACKER_BASE_URL | 배송 추적 API 엔드포인트 | |
FEATURE_PAYMENT_ENABLED | 서버 측 결제 기능 플래그 | |
NEXT_PUBLIC_API_URL | ✅ | 웹에서 바라볼 백엔드 주소 |
NEXT_PUBLIC_KAKAO_APP_KEY | ✅ |
| 문서 | 내용 |
|---|---|
| apps/web/README.md | 웹 프론트엔드 개요 및 개발 원칙 |
| apps/web/docs/ARCHITECTURE.md | 컴포넌트 문맥 기반 그룹화 규칙, i18n 구조 |
| apps/web/docs/CACHING.md | 캐시 4개 층의 책임, 서버 시드 쿼리 대장, 재검증 범위 규칙 |
| assets/brand/README.md | A 펜선 로고 원본·재생성·아이콘·SNS 파일, B 대안과 이전 로고 보존 |
| apps/server/README.md | 백엔드 개요, 모듈 구조, 개발 원칙 |
| apps/admin/README.md | 관리자 포털 (초기 세팅만 된 미배포 앱) |
| core · api-client · react-query | 공용 패키지 사용법 |
| tools/book-ingest/README.md | 운영자 적재 도구 — 신간·자유 검색, 표지 R2, books·book_dimensions 적재 |
모든 기능 폴더에 개별 README가 있습니다.
SEO·SNS 공유 점검 결과: 2026-09-19 SEO 감사 — 주요 페이지 운영 응답, 구조화 데이터 결함, 검색 발견성과 공유 개선 사항.
도서 리뷰 검색 노출: 2026-10-07 리뷰 점검 · 공식 가이드 조사. 공개 리뷰의 검색·OG 제목에 도서명을 보충하고 RSS에 정제한 HTML 본문 전체를 제공합니다. 공개 리뷰 생성·수정·삭제 후 서버 상세 재검증과 네이버 IndexNow 알림을 구현했으며 활성화·배포 순서는 서버 리뷰 문서에 있습니다. 서버 리뷰 링크·페이지네이션은 후속 과제입니다.
Vercel 사용량 점검: 2026-09-20 비용 진단 — 운영 캐시 반복 측정, 배포별 ISR 재생성, 사이트맵·404 렌더 및 응답 크기 분석.
| 문서 | 내용 |
|---|---|
| docs/used-book-pay-implementation.md | 에스크로 결제 시스템 설계·상태 머신·엣지 케이스·단계별 실행 계획 (기능은 현재 플래그로 비활성) |
| docs/book-data-migration-plan.md | 알라딘 API 종료(2026-10-30) 대응 — 표지·서지·검색 탈외부화 계획과 진행 상황 (진행 중) |
| docs/manual-ddl-log.md | 운영 DB에 수동 적용한 DDL 이력 (필독). 실행한 SQL 원본은 docs/ddl/ |
| .agents/rules/ | 코드베이스 컨벤션 — 모노레포/패키지 · 서버 · 프론트엔드 · 체크리스트 |
This project is licensed under the MIT License.
| 카카오 맵 JS SDK 키 |
NEXT_PUBLIC_TOSS_PAYMENTS_CLIENT_KEY | 결제 위젯 클라이언트 키 |
NEXT_PUBLIC_FEATURE_PAYMENT_ENABLED | 웹 측 결제 기능 플래그 |
USER_WEB_URL | 서버가 갱신 요청을 보낼 사용자 웹 주소. 운영 IndexNow는 https://bookjeok.com |
INDEXNOW_ENABLED | 서버 네이버 리뷰 변경 알림 플래그. 기본 false, 운영에서 true |
REVALIDATE_TOKEN | On-Demand ISR 갱신 시크릿 (서버 전용, 폴백 없음) |
NEXT_PUBLIC_GA_MEASUREMENT_ID | Google Analytics |
NEXT_PUBLIC_CLARITY_PROJECT_ID | Microsoft Clarity |
NEXT_PUBLIC_GOOGLE_ADSENSE_ID | Google AdSense |