📚 전체 글

총 102편

📚 NestJS + Refine 풀스택 트러블슈팅

교육과정 도메인 BE 완성과 같은 날 핫픽스 7 건 — NestJS @Cron 2 중 실행 묶음

교육과정 도메인 BE 완성 머지 직후 7 시간 안에 7 건 핫픽스가 묶여 배포된 하루를 정리한다. Class API 폴백 사슬·level displayName 11 곳·login response teacherId 까지는 기능 보완이었지만, 06:00 킥오프 배치가 2 중 실행되며 회원당 과제 2 개가 생성된 사고는 NestJS ScheduleModule.forRoot() 중복 등록의 고전 함정이었다. forRoot 단일화·배치 1.5 단계 만료 로직·Student Home 조회 가드 세 방어 층을 같은 머지에 묶어 1,502 줄 세션 로그가 끝났다.

NestJSPrismaScheduleModule
📚 NestJS + Refine 풀스택 트러블슈팅

교육과정 자동 승급의 늪 — 도메인 버그 3 건 트러블슈팅

그룹 교육과정에 *레벨 25* 를 지정해 둔 회원이 *배치고사 건너뛰기* 직후 *레벨 15* 로 배정된 사고. 표면 증상은 하나였지만 진짜 원인은 *Class 동기화 누락 / getEffectiveCurriculum 폴백 누락 / 배치고사 건너뛰기의 currentLevelId 폴백* 세 도메인 버그가 한 사슬을 이루고 있던 점이었다. 폴백 4 단으로 재정렬하고 자동 승급 메서드 두 개를 들여 *EXCELLENT 3 일 + Top 도달* 조건을 한 흐름에 묶은 A 톤 트러블슈팅을 정리한다.

NestJSPrismaDomain Modeling
📚 NestJS + Refine 풀스택 트러블슈팅

Soft Delete 구현 — deletedAt 한 컬럼이 닿은 27곳의 설계

NestJS + Prisma 프로젝트에서 Member·Class·Operator 세 도메인 모델에 deletedAt 한 컬럼을 도입하며 마주친 27곳의 쿼리 필터, 트랜잭션 dependent 정리, 로그인 차단, FE 다이얼로그 흐름까지 도메인 모델 표준의 구현기를 정리한다. delete-check + DELETE 두 단계 API와 Application Service 16곳 + Domain Repository 11곳의 필터 분포까지 한 흐름으로 묶었다.

NestJSPrismaSoft Delete
📚 NestJS + Refine 풀스택 트러블슈팅

react-hook-form + Zod 폼 표준 정착기

여러 운영 페이지와 외부 SPA 에 흩어진 폼 12 개가 각자 다른 검증·정규화·에러 표시 흐름을 갖고 있었다. zod 공용 스키마를 `lib/validations/` 에 모으고, react-hook-form + zodResolver + shadcn/ui Form 한 흐름으로 묶으면서 입력 정규화·필드 에러·BE 에러 매핑까지 한 줄의 표준으로 정착시킨 구현기.

Reactreact-hook-formzod
📚 NestJS + Refine 풀스택 트러블슈팅

연락처 포맷 통일 — 저장은 숫자만, 표시는 하이픈

한 고객사에서 연락처 010-1234-5678 을 입력하면 BE 400, 01012345678 을 입력하면 통과되는 비대칭이 보고됐다. DB 를 열어 보니 같은 컬럼에 *하이픈 포함* 과 *숫자만* 이 섞여 누적돼 있었다. 진짜 범인은 *저장 포맷·입력 포맷·표시 포맷 세 축의 표준이 어디에도 없었던 점* 이었다. BE `phone.util.ts` + DTO `@Transform/@Matches` + FE zod `transform/refine` + 마이그레이션 SQL 을 *한 머지에 묶어* BE·FE·DB 세 곳을 동시에 정규화한 트러블슈팅을 정리한다.

NestJSPrismazod
📚 NestJS + Refine 풀스택 트러블슈팅

주간 출석 KST 타임존 — 월요일이 사라진 트러블슈팅

Unity 주간 출석 API 가 운영 한 달 후 월요일 칸만 무작위로 비었다. DB 에는 COMPLETED 과제가 분명히 있는데 화면은 null 이었다. 진짜 범인은 `new Date('2026-03-09')` 가 ISO 8601 로 파싱되어 UTC 자정 (= KST 09:00 월) 으로 잡힌 점이었고, batch 가 KST 00:00 으로 고정한 scheduledAt 이 쿼리 범위 *직전 9 시간* 에 들어가 통째로 누락되고 있었다. KST 자정 명시 생성자로 두 줄을 갈아끼우고, ESLint 룰과 jest mocking 가드로 재발을 막은 트러블슈팅을 정리한다.

NestJSPrisma타임존
📚 NestJS + Refine 풀스택 트러블슈팅

1,974줄 풀 백업 — 1인 개발에서 상태 관리하는 법

PM·BE·FE·QA·외부 단말 5개 역할을 한 사람이 워크트리 4개로 돌리는 동안 통신 손실을 막은 단일 파일 운영기. 500줄 임계치를 넘어 1,974줄까지 누적된 하루치 통신을 풀 아카이브로 떨어뜨린 시점의 회고와, 같은 파일 위에서 변경 명세서·LESSONS 갱신·다음 작업 지시가 어떻게 직렬화됐는지 정리한다.

1인 개발워크트리session-sync
📚 NestJS + Refine 풀스택 트러블슈팅

QR 배치고사 + Firebase Hosting 멀티 사이트 배포

Unity 안에 카메라 입력 흐름을 끌어들이는 대신, QR 코드 한 장으로 모바일 외부 웹앱을 띄우는 결정. 인증 없는 Public Submit API, PROCESSING_SPEED 서버 자동 계산, sessionId 폴링 흐름, 그리고 같은 결정 트리의 연장선에서 Level-Test SPA 와 3개 운영 포털을 Firebase Hosting 멀티사이트로 분리 배포한 구현기.

NestJSFirebase HostingUnity
📚 NestJS + Refine 풀스택 트러블슈팅

Cloud SQL 리전 트랩 — US→Taiwan 71% 트러블슈팅

외부 뷰어 리포트 4탭 N+1 을 잡아 2.3 초까지 줄였는데 목표 500ms 와의 마지막 1.8 초가 코드 어디에서도 나오지 않았다. Cloud SQL Query Insights 가 쿼리 자체는 0.1~1ms 라고 답했다. 진짜 범인은 한국 ↔ us-central1 왕복 100~200ms × 응답당 6~8 쿼리 누적이었다. 레플리카 프로모션으로 asia-east1 (대만) 으로 옮기고 .env 두 줄을 갈아끼우자 `/content-candidates` 가 2,340ms → 682ms (71% ↓), `/student/home` 이 2,000ms+ → 400~600ms 로 떨어졌다. 인덱스·N+1 이 아니라 리전·RTT 가 본진이었던 인프라 사고를 정리한다.

GCPCloudSQL성능최적화
📚 NestJS + Refine 풀스택 트러블슈팅

외부 뷰어 리포트 4탭 N+1 — 14초 응답을 2초로

외부 뷰어 리포트 토큰 한 줄로 들어가는 공개 페이지의 4탭 단일 응답이 최대 14.4초까지 늘어났다. Prisma `include` 만 의심하다 진짜 범인을 놓쳤다. 두 층의 N+1 — 4탭 빌더 6 건의 순차 await + 일별·주별 for 루프 안 `findMany` 반복 — 을 Promise.all 병렬화 + 단일 findMany + 메모리 그룹핑으로 풀어 55 개 쿼리를 10 개로, p95 응답을 ~2.3 초로 끌어내린 트러블슈팅을 정리한다.

NestJSPrisma성능최적화
📚 NestJS + Refine 풀스택 트러블슈팅

Framer Motion whileInView — 일부 카드만 안 뜨던 날

외부 뷰어 리포트의 4탭에 인사이트 카드와 빈 데이터 폴백을 같이 깔고 나니 일부 motion.section의 whileInView 애니메이션이 트리거되지 않았다. 같은 컴포넌트가 같은 코드로 어떤 탭에서는 정상이고 어떤 탭에서는 opacity 0으로 그대로 멈췄다. 원인은 조건부 마운트 시점에 IntersectionObserver의 첫 콜백이 framer-motion 이펙트 부착 전에 끝나 버린 레이스 + viewport.once 기본값 false의 합이었다. once:true 표준화 + sentinel ref + ESLint 가드를 같이 깐 트러블슈팅을 정리한다.

Framer MotionReactwhileInView
📚 NestJS + Refine 풀스택 트러블슈팅

외부 뷰어 리포트 인사이트 — 활동 데이터를 자연어로 바꾸기

외부 뷰어가 보는 4탭 리포트에 차트 옆으로 한 줄 자연어 메시지를 붙이는 인사이트 시스템 도입 머지를 정리한다. AI 호출 없이 결정성을 보장하는 룰 기반 메시지 생성기, 5 + 4 + 5 = 14종 긍정 규칙, 빈 데이터 폴백 카피 4종, 4탭별 메시지 슬롯 배치 — 같은 dev 머지 사이클 안에서 응답 표준 + 룰 + 폴백을 동시에 굳힌 설계 흐름과 트레이드오프를 기록한다.

NestJSTypeScript리포트
📚 NestJS + Refine 풀스택 트러블슈팅

외부 뷰어 리포트 v1→v2 토큰 전환 — 가장 길었던 하루

도입 단계 머지가 같은 날 밤 사용자 검토 1회로 폐기된 사고. Parent / ParentStudent / ParentInvitation 3 모델 + 회원가입 단일 트랜잭션 + 별도 JWT 시크릿 흐름 전부 삭제, StudentReport 1 모델 + nanoid 21자 공개 토큰 + 7일 만료 정책으로 같은 dev 머지 사이클 안에 갈아엎은 BE -2,099 / +563 + FE -1,475 / +968 의 갈아엎기 비용과 결정 사유, 사후 평가를 정리한다.

NestJSPrisma토큰 인증
📚 NestJS + Refine 풀스택 트러블슈팅

보호자 외부 뷰어 대시보드 — 모바일 앱·초대 토큰 회원가입

회원 레포트와 같은 데이터를 보호자(외부 뷰어) 그릇으로 옮기는 별도 앱 도입 머지. 모바일 우선 컨테이너(max-w-[430px]), Parent/ParentStudent/ParentInvitation 3 신규 모델, 초대 토큰 + 회원가입 단일 트랜잭션(4 write), 별도 JWT 시크릿(1h access / 30d refresh), 전화번호 = 로그인 ID 결정을 같은 dev 머지 사이클 4시간 안에 BE + FE Mock + FE 인증 연동까지 묶은 도입 단계 마일스톤이다.

NestJSPrismaJWT
📚 NestJS + Refine 풀스택 트러블슈팅

회원 레포트 5탭 API 설계 — 인사이트 3파트 구조

관리자 페이지 회원 레포트 페이지를 5탭(개요·학습 분석·지표 분석·레벨 이력·상세 기록)으로 설계한 머지. 신규 4 API + 기존 1 API 재사용, 공통 기간 필터, 인사이트 3파트(긍정/격려/개선) 구조, 학습 진도 집계 단위를 묶음에서 콘텐츠로 바꾼 결정, 상세 기록의 드릴다운 3계층(과제→묶음→콘텐츠) 응답 표준을 정리한다. NestJS + Prisma 환경에서 명세 우선·FE Mock 선행 워크플로우로 BE 구현 전 사용자 검토를 확보한 도입 단계 마일스톤이다.

NestJSPrismaAPI 설계
📚 NestJS + Refine 풀스택 트러블슈팅

재화 시스템 첫 머지 — 코인 지갑과 거래 원장(Wallet API)

회원 활동 동기 부여용 코인 지갑을 도입한 첫 머지. Prisma 단일 트랜잭션 안에서 잔액 변경과 거래 원장을 함께 기록하고, balanceAfter는 감사용으로 불변 처리한다. 도입 단계(스키마 + Domain Service + Debug API 4개 + 단위 테스트 18개)와 같은 dev 머지 사이클에 함께 들어간 노출 단계 Wallet API 3개(잔액 조회/입금/출금)의 결정·코드·트레이드오프를 정리한다. NestJS + Prisma 환경에서 통화 시스템을 첫 머지로 옮길 때의 스키마 분리, 응답 표준화, 옵셔널 멱등성, 클라이언트 신뢰 모델을 다룬다.

NestJSPrisma통화 시스템
📚 NestJS + Refine 풀스택 트러블슈팅

콘텐츠 후보 선택 3차 최적화 — 단일 쿼리로 옮기기

회원이 번들에서 콘텐츠 1건을 고를 때마다 호출되는 후보 추천 API는 4단계 폴백(level+metric → 유사 지표 → level only → 전체)이 각각 별도 쿼리를 내던 구조였다. 본 글은 페널티 기반 순위 조정으로 제외 로직을 비우고(v2.1.5), 레벨 콘텐츠를 한 번만 조회해 메모리 풀로 옮긴 뒤, FB0~FB2 폴백을 모두 인메모리 필터로 처리해 4,670ms를 2,340ms로 떨어뜨린 3차 최적화 마일스톤을 정리한다. NestJS + Prisma 환경에서 다단계 폴백 쿼리를 단일 쿼리 + 메모리 필터링 패턴으로 옮길 때의 결정·코드·트레이드오프를 다룬다.

NestJSPrisma성능 최적화
📚 NestJS + Refine 풀스택 트러블슈팅

NestJS 권한 가드 — 목록은 막고 상세는 뚫린 날

운영자가 본인 담당 클래스 1개만 떠야 하는데 모든 클래스가 떴다. 목록 API에 operatorId 필터를 깔고 끝낸 줄 알았는데, 직접 URL로 미담당 클래스 ID를 두드리니 상세·수정·승인 5개 엔드포인트가 그대로 200을 돌려줬다. 원인은 JWT payload.sub(User ID)와 Operator 테이블 id(Operator ID)의 분리 + validateClassAccess 헬퍼 부재 둘이었다. 라운드 한 번에 BE → QA → 추가 BE → QA 재검증으로 닫은 NestJS ForbiddenException + Prisma classOperator.findFirst 패턴을 정리한다.

NestJSPrismaJWT
📚 NestJS + Refine 풀스택 트러블슈팅

Problem 종속 끊기 — 1,891개 마이그레이션과 단위 테스트 38건

이전 편의 배치고사 MVP 머지가 가능했던 것은 같은 날 오후에 Problem 모델의 콘텐츠 종속을 먼저 끊었기 때문이다. 본 작업은 Problem 테이블이 ContentItem에 종속돼 재사용 불가하던 구조를 끊고, 1,891개 문제를 신 스키마로 옮기고, Admin/Student 8 엔드포인트와 단위 테스트 38건을 한 머지에 묶은 마일스톤이다. 스키마·마이그레이션·API·테스트가 한 dev 머지 사이클에 들어간 BE 작업 기록을 정리한다.

NestJSPrismaProblem
📚 NestJS + Refine 풀스택 트러블슈팅

배치고사 MVP 후속 — 명세를 코드로 옮기고 레거시 571줄을 일괄 삭제하다

이전 편에서 배치고사 명세를 자동 레벨 배치 폐기로 정리했다. 같은 날 저녁 그 명세를 실제 코드·테스트·시드 데이터로 한 번에 옮긴 마일스톤이다. 도메인·응용 갱신, Legacy 컨트롤러 3개 + 도메인 서비스 3개 + DTO 9건 571줄 삭제, 단위 테스트 22개, 회원 시드 보강, QA E2E 5 시나리오까지 5개 커밋이 한 머지 사이클에 들어간 운영 기록을 정리한다.

NestJSPrisma배치고사
📚 NestJS + Refine 풀스택 트러블슈팅

Unity Lobby + 배치고사 씬 통합 — 두 클라이언트가 같은 회원을 보는 첫 빌드

Unity 네이티브 클라이언트의 Lobby 씬과 배치고사 씬을 같은 빌드 안에 묶었다. NestJS 응답 모델과 Unity C# 모델의 필드명 불일치, NetworkManager 단일 진입점 래퍼, AuthGuard 패턴, 배치고사 완료 시 첫 숙제 자동 발행 체인, contentUrl 전체 경로 정책까지 6가지 설계 결정과 트레이드오프를 정리한다.

UnityNestJSVuplex
📚 NestJS + Refine 풀스택 트러블슈팅

1인 개발 QA 5라운드 — 타이머·시드·스키마로 옮긴 버그들

혼자서 BE·FE·QA·PM 네 역할을 갈아끼우며 학습 클라이언트 웹 프로토타입의 QA 라운드 3~7을 연속으로 돌렸다. 라운드 3의 타이머 NaN·시드 부족부터 라운드 7의 BundleContent nullable·다음 번들 자동 생성까지, 5라운드 동안 버그가 BE·시드·스키마를 차례로 옮긴 흐름과 혼자서 여러 역할을 맡는 QA의 회고.

QA1인 개발워크트리
📚 NestJS + Refine 풀스택 트러블슈팅

타이머가 NaN:NaN으로 떴다 — Bundle API 응답 누락 필드와 비어 있는 콘텐츠 후보

QA Round 2에서 EC-3 엣지 케이스가 잡은 타이머 NaN:NaN 버그. 같은 컴포넌트가 같은 코드로 한 화면에서 정상, 다른 화면에서 NaN:NaN으로 뜨던 패턴이다. 원인은 BE 응답 DTO에서 한 필드 누락 + FE 무방어 + seed 데이터 부족 세 가지의 합이었다. 응답 스키마 검증과 콘텐츠 후보 0건 방어를 같이 깐 라운드 회고.

ReactTypeScriptNestJS
📚 NestJS + Refine 풀스택 트러블슈팅

혼자 여러 역할로 QA 1차 — 브랜치 미동기화와 잔존 토큰의 함정

혼자서 BE·FE·QA·PM을 동시에 맡는 환경에서 학습 클라이언트 웹 프로토타입 첫 QA 라운드를 돌렸다. 브랜치 미동기화로 인한 미구현 오보, 만료 토큰 잔존으로 인한 401, UI 스모크 수준의 검증 충실도 부족까지 세 함정을 한 라운드에 만났고, 다음 라운드부터의 운영 체크리스트로 정리했다.

QA1인 개발워크트리
📚 NestJS + Refine 풀스택 트러블슈팅

킥오프 배치 첫 구현 — 매시 전체 EXPIRED 사고와 Winston 도입

첫 배치 작업으로 짠 NestJS 킥오프 배치(@nestjs/schedule)가 @Cron('EVERY_HOUR') + findExpiredAssignments() 시간 체크 누락 두 함정에 걸려 모든 ACTIVE 숙제를 매시간 EXPIRED로 굳혀 버린 사고를 복기한다. fix는 두 줄(시간 비교 + cron 표현식)이었지만, 사고가 일어났는데도 콘솔이 흘러가 흔적이 없었던 점이 더 컸다. 콘솔 일변도에서 Winston 파일 로깅(daily-rotate, app/error 분리, GCP severity) 으로 갈아탄 결정과 설정 전문을 정리한다.

NestJSCronBatch
📚 NestJS + Refine 풀스택 트러블슈팅

지표 누계 시스템 — TOP5 순위를 INSERT 전용 스냅샷으로 굳히기

다섯 개 지표의 점수를 가중평균으로 0~100 범위에 수렴시키던 설계를 폐기하고, 누계 점수를 매 묶음 완료마다 INSERT 전용으로 쌓아 distinct로 최신 1행씩 읽어 TOP1~5 순위를 굳히는 스냅샷 시스템을 짠다. 결정 5건, 트레이드오프, 두 진입점(배치고사 완료·묶음 완료)에서의 호출 패턴, 회복성 try-catch까지 코드 인용으로 정리한다.

NestJSPrismaSnapshot
📚 NestJS + Refine 풀스택 트러블슈팅

콘텐츠 브릿지 10종 통합 완료 — 같은 규격으로 묶기

Unity WebView 안에서 동작하는 웹 콘텐츠 10종을 같은 PostMessage 브릿지 규격으로 묶는 마일스톤 작업을 정리한다. bridge/ 폴더 하드카피와 useProblemResults 훅으로 콘텐츠별 차이를 흡수하고, 통계 필드 8개·2개·contentType·problems 배열을 네 차례에 걸쳐 점진적으로 확장하면서 마주친 400 Bad Request·accuracyPct 재정의·시간 정확도 변환 로직 회수의 결정 사유와 트레이드오프를 기록한다.

Content BridgePostMessageWebView
📚 NestJS + Refine 풀스택 트러블슈팅

Unity ↔ 웹 PostMessage 브릿지 설계기

Unity 네이티브 앱이 WebView로 웹 콘텐츠를 임베드할 때, 호스트와 콘텐츠는 PostMessage로만 대화한다. Vuplex·iframe·Standalone 세 환경을 런타임에 자동 감지하는 ContentBridge 모듈을 설계하고, contentInit·contentReady·contentResult·contentExit 메시지 규격을 한곳에 고정한 과정을 정리한다. 핵심 제약은 Standalone 모드에서 기존 콘텐츠 코드가 100% 그대로 동작해야 한다는 것이었다.

UnityWebViewPostMessage
📚 NestJS + Refine 풀스택 트러블슈팅

NestJS Swagger 일괄 적용 — 35개 컨트롤러 + DTO 22개

Swagger UI는 멀쩡한데 모듈마다 빠진 데코레이터·interface DTO 때문에 API 문서가 실제 동작과 어긋났다. 35개 컨트롤러에 @ApiTags·@ApiBearerAuth를 일괄 적용하고, 한 모듈의 9개 메서드·22개 DTO를 interface에서 class로 한 번에 정리했다. 모듈별 점진 적용을 거절하고 일괄 적용을 택한 트레이드오프와 적용 패턴을 정리한다.

NestJSSwaggerOpenAPI
📚 NestJS + Refine 풀스택 트러블슈팅

디버깅용 운영 API 7개 — Unity 만료 테스트 30분 대기를 0초로

Unity 클라이언트 QA에서 할당 만료를 보려고 30분을 기다리거나 DB를 직접 건드리던 흐름을 운영용 디버그 API 7개로 흡수했다. PLATFORM_ADMIN 단독 권한·환경 토글·멱등 reset 같은 설계 결정과 후속 개선(loginId 쿼리, createNewAssignment 옵션)까지의 트레이드오프를 정리한다.

NestJS디버깅 API운영 도구
📚 NestJS + Refine 풀스택 트러블슈팅

JWT Guard 적용 — request.user undefined부터 jwt malformed까지

활동 로그 인터셉터에서 request.user가 undefined로 잡혔다. 원인을 파고드니 JwtAuthGuard 구현 누락, Auth Service와 Guard 간 JWT_SECRET 불일치, FE의 base64(JSON) Mock 토큰까지 3중 함정이 차례로 드러났다. 새벽 3시 임시 우회부터 다음 날 17시 정상화까지의 디버깅 흐름과 NestJS 글로벌 Guard·명시적 secret 검증·실제 BE API 시드 패턴을 정리한다.

NestJSJWTGuard
📚 NestJS + Refine 풀스택 트러블슈팅

교육과정 구조 리팩토링 — 3필드 분리와 폴백 결정기

교육과정 목표를 Member 한 곳에만 두면 운영 비용이 폭증한다. Prisma 두 컬럼 추가 + 도메인 서비스 한 메서드로 Member → Class → 분기 기본값 3계층 폴백을 한 곳에 모은 리팩토링. 응답에 출처(curriculumSource)를 함께 실어 운영자 UI도 한 화면에 결론을 보여준다.

NestJSPrismaPostgreSQL
📚 NestJS + Refine 풀스택 트러블슈팅

멀티테넌트 쓰기 가드 — body.tenantId 차단과 집계 일관성

PATCH body에 끼워 넣은 다른 tenantId가 통과해 옆 고객사 회원이 옮겨 쓰였다. NestJS Guard + DTO에서 tenantId 제거 + whitelist:true 3중 차단으로 쓰기 가드를 만들고, count/sum/groupBy는 where 변수 추출 + $transaction으로 묶었다. 시간 슬라이스는 KST 자정 고정, 멱등성은 Redis EX 60 + 충돌 검사 + audit, 정책 위반은 bypassPolicy + reason + audit. ts-morph 정적 스캔까지 1일 트러블슈팅.

NestJSRefine멀티테넌트
📚 NestJS + Refine 풀스택 트러블슈팅

Prisma 그래프 스키마 — 선형 레벨을 DAG로 옮긴 4가지 결정

단일 정수 sortOrder로 줄세운 선형 레벨을 노드/엣지 분리 DAG로 옮겼다. 셀프 참조 1:N vs 별도 엣지 테이블, PostgreSQL recursive CTE로 진행도 계산, 엣지 INSERT 시점 사이클 검출, 선형 i→i+1 자동 마이그레이션 4가지 결정과 zod invariants e2e로 회귀를 차단한 트러블슈팅.

PrismaPostgreSQLDAG
📚 NestJS + Refine 풀스택 트러블슈팅

두 번째 점검은 합류 지점이었다 — Admin Portal 2차에서 한 사이클에 잡힌 FE-BE 연동 버그 11건

1차 점검(SC-A01~A17)이 *FE 단독·BE 단독*의 단위 검수였다면, 2차 점검은 *FE-BE를 처음 같이 돌려 보는 자리*였어요. 그날 자정부터 새벽까지 한 사이클에 11건이 터졌습니다. CORS PATCH·DTO interface→class·운영자 상태 변경 400·콘텐츠 FK 제약·dbVersion 동적 조회 다섯 자리(BE)에 모니터링 useCustom·진단평가 문제 추가 버튼 결정·이메일 인라인 에러·SUPER_ADMIN 활성화 가드·배치고사 상태 엔드포인트 분리·콘텐츠 목록 null 크래시 여섯 자리(FE)까지. 코드 변경은 한 줄짜리 자리가 많았는데, *어떤 자리가 깨질 수 있는지*를 미리 보지 못한 게 11건이 한꺼번에 몰린 이유였어요. 합류 지점이라는 디버깅 환경의 본질을 11개의 자리로 짚은 글입니다.

NestJSRefineCORS
📚 NestJS + Refine 풀스택 트러블슈팅

CORS는 됐다 — PATCH만 빼고. allowedHeaders 한 줄과 Vite 프록시의 소문자 메서드

DTO를 class로 바꾼 다음 날 새벽, 같은 PATCH 흐름에서 이번엔 CORS가 빨갛게 물들었어요. 'Method patch is not allowed'. 메서드 이름이 소문자였습니다. 한쪽은 NestJS의 `allowedHeaders` 한 줄이 비어 있어서, 다른 한쪽은 React Admin의 dataProvider가 `method`를 소문자로 그대로 넘기고 있어서 — 그리고 그걸 Vite 6 프록시가 정규화 없이 그대로 흘려보내서. 두 자리를 동시에 잡고, `.env.local`을 절대 URL에서 `/api` 프록시 경유로 바꾸고, OPTIONS 응답 헤더를 검수 체크리스트로 박은 자정 디버깅 6시간을 다시 짚었습니다.

NestJSCORSVite
📚 NestJS + Refine 풀스택 트러블슈팅

Mock에선 되던 게 REST에선 안 됐다 — 응답 포맷 한 칸 차이가 만든 하루

VITE_USE_MOCK_API=false로 토글한 순간 useTable이 멈췄다. BE는 TransformInterceptor로 모든 응답을 `{ success, data, meta }`로 표준화했고, Refine은 `{ data, total }`을 기대했다. dataProvider 어댑터 한 줄로 메우면 끝나는 줄 알았는데 — 경로가 `/api/v1/api/v1/...`로 중복되고, FE가 `order`로 보낸 정렬 키를 BE는 `sortOrder`로 받았으며, 중첩 리소스 `/contents/:id/problems`는 dataProvider 기본 구현이 못 그렸다. 결국 어댑터 한 줄이 아니라 경로 정규화·필드 매핑·중첩 리소스 라우터·401 인터셉터까지 네 자리를 박아야 토글이 진짜 한 줄이 됐다.

RefineNestJSDataProvider
📚 NestJS + Refine 풀스택 트러블슈팅

1인 다역으로 5일 만에 90% — Admin Portal MVP를 끌어올린 토글 한 줄

SC-A01부터 SC-A20까지 20개 시나리오를 5일 만에 75%에서 90%까지 끌어올린 Admin Portal MVP 사이클. 혼자 PM·BE·FE 세 역할을 돌리면서 만든 병렬화의 환상은 사실 코드 한 줄 — `VITE_USE_MOCK_API=true` 환경변수 토글과 그 뒤에 붙은 Mock/REST DataProvider 두 구현체에서 시작됐다. Refine + Vite 위에서 14개 page 모듈을 어떤 순서로 박았는지, [FE → PM] 태그로 셀프 보고하던 협업 프로토콜이 왜 효과가 있었는지, Mock에서 REST로 갈아끼울 때 실제로 바뀐 건 한 줄이었는지 — 다섯 H2로 정리한다.

RefineViteNestJS
📚 NestJS + Refine 풀스택 트러블슈팅

갈아엎고 80일 — v2.0 마이그레이션 8편 메타 회고

devlog-13부터 devlog-20까지, v2.0 갈아엎기 5편과 v2.1로 끌어올리기 2편, v3.0 한 단계 더 1편을 한 발 떨어져서 다시 읽는다. Block에서 Bundle로의 전환, 5-Phase로 쪼갠 마이그레이션, 같은 날 v2.1로 SSoT를 끌어올린 결정, 1,682줄을 한 커밋에 박은 도메인 레이어, 그리고 그 위에 얇은 막을 입힌 Application Service까지. Before/After가 정말로 달라진 다섯 축(코드 책임, 문서 트랙, 멘탈, 도메인 명확성, 테스트 가능성)과 80일이 지나도 안 달라진 한 축(혼자 결정하고 혼자 박는 운영 모드)을 코드 위에서 정리한다.

DDD마이그레이션NestJS
📚 NestJS + Refine 풀스택 트러블슈팅

v3.0 Application Layer 재작성 — 도메인 서비스 위에 얇은 막을 한 Phase에 박은 날

도메인 서비스를 박은 다음 그 위에 Application Service 4종 + Controller 4종 + DTO 4종을 한 번에 박은 결정 이야기. trackState, secondLevel, track1/2Completed, curriculumProgress 같은 v3.0 필드가 Application 경계에서 어떻게 흡수되는지, 그리고 그 결과로 터진 빌드 에러 51개를 다섯 카테고리로 잡아간 새벽의 디버깅 흐름까지. 도메인이 비즈니스 규칙을 가지고, Application은 그것을 호출하는 얇은 오케스트레이션 막이 된다는 결합 규약을 코드 위에서 다시 한 번 확인한 한 Phase의 기록.

DDDApplication LayerNestJS
📚 NestJS + Refine 풀스택 트러블슈팅

v2.1 Domain Layer — 도메인 서비스 1,682줄을 한 커밋에 박은 날의 설계 철학

전날 4,658줄짜리 DDD 문서를 박은 다음 날 저녁, 그 문서를 입력으로 받아 도메인 레이어 코드를 다시 박았다. 한 커밋에 1,682줄 추가. MetricRank TOP1~5와 V21_THRESHOLDS를 도메인 타입으로 박은 결정, ContentCandidateService의 후보 폴백 설계, 90% 이상도 복습 필수로 바꾼 ReviewModuleService 리팩토링, BundleCompleted 이벤트로 Aggregate 간 결합을 끊은 결정 — 다섯 개의 도메인 서비스가 같은 커밋에 들어간 이유와 각각의 설계 철학.

DDDDomain LayerNestJS
📚 NestJS + Refine 풀스택 트러블슈팅

코드를 박은 다음 날 — 4,658줄 DDD 문서를 24분 사이에 다시 쓴 하루

v2.0 코드 마이그레이션이 끝난 직후, 같은 날 밤 24분 사이에 두 개의 docs 커밋이 들어갔다. v2.0 DDD 문서들은 _archived/v2.0/으로 묻혔고, 마스터 문서가 v2.1로 진화하면서 1,354줄이 갈아 끼워졌고, 그 위에 새 DDD 문서 4,658줄이 새로 적혔다. 코드와 문서를 다른 트랙으로 두지 않기 위해 무엇을 묶었고 무엇을 일부러 묻었는지의 기록.

DDDSSoT문서화
📚 NestJS + Refine 풀스택 트러블슈팅

Phase 3-3·3-4·3-5 — Application부터 Module까지, v2.0 마이그레이션 닫는 날

Phase 3-1·3-2가 만든 Repository와 Domain Service 위에 Application(953줄), Controller+DTO(763줄), Module(39줄)을 차례로 얹어 v2.0 번들 기반 학습 시스템을 닫는 단계. UC-06~10과 UC-14~17을 어떻게 코드로 옮겼는지, 인메모리 챌린지 스토어를 왜 일부러 남겼는지, 39줄짜리 모듈 한 장이 왜 끝의 끝인지를 기록한다.

NestJSApplication ServiceController
📚 NestJS + Refine 풀스택 트러블슈팅

Phase 3-1·3-2 — Repository와 Domain 서비스로 36개 빌드 에러 잡기

Phase 2가 남긴 36개 빌드 에러를 봉합하는 단계. BundleRepository 인터페이스 204줄과 Prisma 구현체 294줄, BundleGenerationService 417줄을 어떻게 분리해 작성했는지, 5콘텐츠 고정 구조와 4단계 폴백 전략을 코드로 어떻게 옮겼는지의 기록. 인터페이스 분리가 멘탈에 어떤 보호막을 쳐줬는지 포함.

NestJSPrismaDDD
📚 NestJS + Refine 풀스택 트러블슈팅

Phase 2 스키마 마이그레이션 — 데이터 안 날리고 구조 바꾸기

v2.0 Phase 2는 self-reference를 배열로 바꾸고, enum 두 개를 추가하고, NOT NULL을 nullable로 푸는 스키마 대수술이었다. Prisma migrate가 자동 생성한 SQL의 'data will be lost' 경고 4개를 어떻게 무력화했는지, 그리고 마이그레이션 직후 36개 빌드 에러가 났는데도 왜 멘탈이 멀쩡했는지의 기록.

NestJSPrismaPostgreSQL
📚 블로그 SEO 실험실

Google E-E-A-T를 개인 블로그에 실제로 적용하는 방법 — 공식 문서 기반 실전 가이드

Google E-E-A-T(경험, 전문성, 권위, 신뢰)를 개인 기술 블로그에 실제로 적용한 과정을 정리합니다. 품질 평가 가이드라인을 읽고, 환경 명시 박스·레퍼런스 박스·구조화 데이터·시리즈 구조를 도입하기까지 — 공식 문서에서 근거를 찾고 하나씩 적용한 기록입니다.

SEOE-E-A-TGoogle
📚 1인 인프라 구축기

Claude Max 플랜으로 API 호출하면 429가 뜨는 이유 — 인증 체계 5단계 완전 정리

Claude Max 플랜의 OAuth 토큰(sk-ant-oat)으로 Messages API를 직접 호출하면 429 Rate Limit이 뜹니다. Claude Code의 인증 우선순위 5단계, sk-ant-oat vs sk-ant-api 차이, 그리고 스크립트에서 Max 플랜을 활용하는 우회법을 실제 트러블슈팅 사례와 함께 정리합니다.

ClaudeClaude CodeAnthropic API
📚 NestJS 실전 트러블슈팅

prisma generate 누락 — 빌드는 되는데 런타임 에러가 나는 이유

NestJS + Prisma 프로젝트에서 schema.prisma에 새 필드를 추가한 뒤 prisma generate를 빠뜨리면, TypeScript 빌드는 as any 캐스팅 덕에 통과하지만 런타임에서 Prisma Client가 새 필드를 모른다. pnpm build와 prisma generate가 별개 명령인 구조적 함정, prebuild 훅으로 자동화하는 해결법, Docker와 CI에서 놓치지 않는 예방 전략을 실전 코드와 함께 정리한다.

NestJSPrismaprisma generate
📚 React 프론트엔드 삽질기

Refine useCustom config.query가 정수를 보장하지 않는 함정 — 타입은 number인데 왜 400이야?

Refine의 useCustom hook에서 config.query 객체에 number 타입 값을 전달해도, URL 쿼리 파라미터로 직렬화되면서 문자열이 된다. NestJS @Type(() => Number) 검증과 조합하면 targetClassId must be an integer number 400 에러가 터진다. URL 직접 삽입 패턴으로 해결한 실전 사례를 정리한다.

ReactRefineuseCustom
📚 NestJS 실전 트러블슈팅

Soft Delete 필터가 빠진 곳 찾기 — 삭제한 데이터가 되살아나는 미스터리

NestJS + Prisma 프로젝트에서 Soft Delete 필터를 Application Service에만 적용하고 Domain Repository를 누락하면, 삭제된 데이터가 배치 프로세스와 조회 API에서 좀비처럼 되살아난다. PM 코드 리뷰에서 11곳 추가 발견된 실전 사례를 통해, 레이어별 필터 점검 체크리스트와 grep 기반 검증법을 정리한다.

NestJSPrismaSoft Delete
📚 React 프론트엔드 삽질기

Framer Motion whileInView 애니메이션이 스크린샷에서 사라지는 이유와 해결법

Framer Motion whileInView로 만든 스크롤 애니메이션이 Chrome DevTools MCP 스크린샷이나 OG 이미지 생성에서 보이지 않는 원인은 IntersectionObserver의 뷰포트 의존성이다. initial hidden 상태가 캡처되는 근본 원인과 3가지 해결 전략을 실전 코드로 정리한다.

Framer MotionReactwhileInView