구독권을 구매한 팬이 아티스트와 실시간으로 대화하는 서비스의 실사용자용 웹 프론트엔드. Next.js 15(App Router) + React 19 + TypeScript. 백엔드 MSA(게이트웨이 8080)와 연동한다.
이 문서는 백엔드 스택 구동과 프론트엔드 구동 방법을 함께 다룬다.
- Node.js 20+ & npm
- Docker Desktop (백엔드 인프라·서비스 컨테이너 실행)
- Git (백엔드
config-server가 원격 config 저장소를 클론) - (선택) JDK 25 — 서비스를 도커 없이 로컬 실행할 때만
twogetter/
├── backend/ # Spring Cloud MSA (게이트웨이·서비스·인프라)
└── frontend/ # 이 프로젝트 (Next.js)
백엔드는 config-server + eureka + gateway + 7개 서비스 + 인프라(PostgreSQL·MongoDB·Redis·Kafka)로 구성된다.
전부 docker compose 로 띄운다.
backend/.env 에 아래 값을 채운다 (.env 는 gitignore).
# 원격 config 저장소 (필수 — 없으면 config-server 부팅 불가)
CONFIG_REPO_URI=https://github.com/twogetter/config-repo.git
CONFIG_REPO_BRANCH=main
CONFIG_REPO_USERNAME=<github-username>
CONFIG_REPO_TOKEN=<github-personal-access-token>
# PostgreSQL 슈퍼유저 (컨테이너 초기화용)
DB_USERNAME=<postgres-user>
DB_PASSWORD=<postgres-password>
# MongoDB (product-service)
MONGO_ROOT_USERNAME=root
MONGO_ROOT_PASSWORD=<any-root-password>
# ⚠️ 반드시 config-repo product-service.yml 의 mongodb.password 와 동일해야 한다(기본 product_pass).
# 첫 기동 시 mongo-init 이 이 값으로 product_user 를 생성하기 때문.
MONGO_APP_PASSWORD=product_pass서비스별 PostgreSQL DB/계정(
authdb/auth_user등)은 첫 기동 시docker/postgres-init/init.sh가 자동 생성한다.
cd backend
# 인프라 먼저 (postgres·mongodb·redis·kafka)
docker compose up -d postgres mongodb redis kafka
# MongoDB 초기화(product_user·인덱스 생성)
docker compose --profile local up mongo-init
# config-server → eureka → gateway + 서비스 전체 (첫 빌드는 수 분 소요)
docker compose up -d --build \
config-server eureka-server api-gateway \
auth-service user-service product-service payment-service \
order-service notification-service chat-service또는 한 번에:
cd backend && docker compose up -d --build
docker-compose.yml에는 로컬 검증용 오버라이드가 이미 포함돼 있다(auth/user datasource·jwt, gatewayJWT_SECRET, notification 호스트 등). config-repo 로 정식 이관 전까지의 임시 설정이다.
# 모든 서비스가 Eureka 에 등록됐는지
curl -s http://localhost:8761/eureka/apps -H 'Accept: application/json' \
| python3 -c "import sys,json;print(sorted(a['name'] for a in json.load(sys.stdin)['applications']['application']))"
# → ['AUTH-SERVICE','CHAT-SERVICE','GATEWAY-SERVER','NOTIFICATION-SERVICE','ORDER-SERVICE','PAYMENT-SERVICE','PRODUCT-SERVICE','USER-SERVICE']
# 게이트웨이 경유 회원가입/로그인 스모크 테스트
curl -s -X POST http://localhost:8080/api/auth/signup -H 'Content-Type: application/json' \
-d '{"email":"test@test.com","password":"Password123!","nickname":"테스터"}'| 대상 | 주소 |
|---|---|
| API 게이트웨이 (프론트 진입점) | http://localhost:8080 |
| Eureka 대시보드 | http://localhost:8761 |
| Config Server | http://localhost:8888 |
| auth / user / product / payment / order / notification / chat | 8081 / 8100 / 8200 / 8300 / 8400 / 8500 / 8600 |
| Kafka UI | http://localhost:8989 |
| PostgreSQL / MongoDB / Redis / Kafka | 5432 / 27017 / 6379 / 9092 |
cd frontend
npm install
npm run dev # http://localhost:3000브라우저에서 http://localhost:3000 → 회원가입 → 로그인 후 이용한다.
브라우저 CORS 를 피하기 위해 Next.js 가 서버사이드로 프록시한다(next.config.ts rewrites).
| 프론트 경로 | 프록시 대상 | 용도 |
|---|---|---|
/api/* |
게이트웨이 http://localhost:8080 |
대부분의 API (JWT→X-User-Id 주입) |
/stream/notifications/* |
notification http://localhost:8500 |
알림 SSE (EventSource 는 JWT 헤더 불가 → 게이트웨이 우회) |
| WebSocket (STOMP) | ws://localhost:8600/ws 직접 |
실시간 채팅 수신 (아래 3장 참고) |
기본값으로 로컬에서 바로 동작하며, 필요 시 .env.local 로 재정의한다.
GATEWAY_URL=http://localhost:8080
NOTIFICATION_URL=http://localhost:8500
NEXT_PUBLIC_CHAT_WS_URL=ws://localhost:8600/ws
# 브랜드페이 등록 화면(payment-service 서버렌더)은 브라우저가 직접 열므로 NEXT_PUBLIC_ 이어야 한다.
NEXT_PUBLIC_PAYMENT_URL=http://localhost:8300| 라우트 | 설명 | 서비스 |
|---|---|---|
/login, /signup |
로그인·회원가입 | auth |
/ |
홈 — 구독권(아티스트) 탐색 | product |
/search |
검색 | product |
/products/[id] |
상품 상세 | product |
/subscribe/[id] |
구독 결제 확인 | order · payment |
/mypage |
프로필·탈퇴 | user |
/mypage/subscriptions |
내 구독 | order |
/mypage/payment-methods |
결제수단 | payment |
/chat, /chat/[roomId] |
채팅(목록·대화) | chat |
/notifications |
알림(실시간 SSE) | notification |
채팅은 REST 전송 + WebSocket(STOMP) 실시간 수신 하이브리드다. WebSocket 은 chat-service 에 기본 내장되어
있어 별도 절차 없이 docker compose up 만으로 동작한다.
- 필수 조건: chat-service 의 STOMP 인증(
JwtProvider)이jwt.secret을 요구한다.docker-compose.yml의 chat-service 에JWT_SECRET(gateway·auth 와 동일한 공유 시크릿)이 이미 주입돼 있다 — 이 값이 없으면 chat-service 가jwt.secret 프로퍼티 누락으로 부팅에 실패한다. - STOMP 계약 (프론트
app/lib/chatSocket.ts가 이에 맞춰 구현됨):- 엔드포인트
ws://localhost:8600/ws, CONNECT 헤더Authorization: Bearer <accessToken> - 팬 구독
/sub/rooms/{roomId}/artist, 아티스트 구독/sub/rooms/{roomId}/fan - 메시지 전송은 REST(
POST /api/chat/rooms/{id}/messages) → 커밋 후 상대 대역으로 broadcast
- 엔드포인트
- 프론트는 WS 로 실시간 수신하고, 연결이 끊기면 폴링으로 폴백한다(채팅방 우측
● 실시간/○ 폴링배지).
게이트웨이는 WebSocket 라우팅이 없어 프론트는 chat-service(8600) 에 직접 붙는다. 도메인/게이트웨이 경유가 필요하면
NEXT_PUBLIC_CHAT_WS_URL로 지정한다.
갓 등록한 상품은 PENDING_OPEN 이라 홈 목록(=ACTIVE)에 안 보인다. 데모용으로 노출시키려면:
# 상품 등록(게이트웨이 경유, 로그인 토큰 필요) 후 Mongo 에서 ACTIVE 전환
docker exec bubbletea-mongodb-1 sh -c \
'mongosh --quiet -u product_user -p "$MONGO_APP_PASSWORD" --authenticationDatabase productdb productdb \
--eval "db.products.updateMany({status:\"PENDING_OPEN\",deleted:false},{\$set:{status:\"ACTIVE\"}})"'채팅방은 상품(아티스트) 등록 시 자동 생성되지만, 구독 활성화(결제 성공)가 있어야 팬이 입장한다.
결제(Toss)는 테스트모드다. 브랜드페이 카드 등록은 payment-service 가 서버렌더하는 페이지에서 진행하고,
프론트 /mypage/payment-methods 에서 결제수단을 앱으로 끌어온다.
/mypage/payment-methods→ 카드 등록 · 약관동의 (브랜드페이) — 새 창으로http://localhost:8300/brandpay/checkout?userId={memberId}가 열린다.- 새 창에서 결제수단추가 → 토스 인증 후 테스트 카드 등록. (최초 1회는 인증 리다이렉트로
/brandpay/callback-authJSON 화면에 도달한다 — 뒤로가기로 돌아와 다시 누르면 된다.) - 같은 창에서 정기 자동결제 약관동의 — 이걸 해야 다음 단계가 통과한다.
- 앱으로 돌아와 등록한 카드 불러오기 → 카드별 정기결제로 지정.
⚠️ Toss 의METHOD_UPDATED웹훅은localhost:8300에 도달하지 못한다. 그래서 카드 등록 결과는 웹훅이 아니라POST /api/payments/methods/sync(온디맨드 동기화) 로 반영한다. 브랜드페이 창은 카드 등록 직후 이 엔드포인트를 자체 호출하며, 실패했을 때를 위해 앱에도 "등록한 카드 불러오기" 버튼을 둔다. 웹훅 경로 자체를 검증하려면 ngrok 등 공개 터널이 필요하다.
⚠️ 구독 주문은type=BILLING카드만 승인한다(BillingService.createReadyPayment). 동기화된 카드는NORMAL로 저장되므로 4번의 "정기결제로 지정"을 반드시 거쳐야 한다./subscribe/[id]는 BILLING 카드만 선택지로 노출한다.
자세한 배경은 docs/frontend-backend-integration-analysis.md 참고.
npm run dev중npm run build를 돌리지 말 것 — 같은.next캐시를 덮어Cannot find module './xxx.js'로 깨진다. 깨졌으면rm -rf .next후 dev 재시작.- product-service 500 / Mongo Authentication failed —
MONGO_APP_PASSWORD가 config-repoproduct-service.yml의 값(product_pass)과 불일치..env를 맞추고 Mongo 볼륨을 재생성하거나product_user비밀번호를 갱신. - auth/user/gateway 부팅 실패 — config-repo 에 해당 서비스 설정 공백.
docker-compose.yml의 env 오버라이드가 있어야 부팅된다(이미 포함). - chat-service 부팅 실패
jwt.secret 프로퍼티 누락— chat-service 의 WebSocket 인증이JWT_SECRET을 요구한다.docker-compose.yml의 chat-service env 에JWT_SECRET(gateway·auth 와 동일값)이 있어야 한다(이미 포함). - 알림 SSE 가 안 붙음 — notification-service(8500) 기동 여부 확인. 프론트는
/stream/notifications/connect로 8500 에 직접 붙는다. - Docker 데몬 미실행 — 어떤 서비스도 부팅 불가. Docker Desktop 을 먼저 켠다.
docs/frontend-backend-integration-analysis.md— 남은 후속 ↔ 프론트 연결점docs/payment-gateway-routing.md— 결제 게이트웨이 라우팅(보류)