해커톤 MVP에서 상용 SaaS로 전환하며 적용한 10가지 핵심 변경 사항 정리. 보안 강화, JWT 인증, KO/EN 다국어 지원, 결제 자동화, 에러 모니터링, DB 정규화 등 '돈을 받을 수 있는 서비스'로 만들기 위한 우선순위별 작업 내역 분석.
개요
해커톤에서 3일 만에 만든 WICHI MVP는 “작동은 하지만 돈을 받기엔 부족한” 상태였습니다. 핵심 분석 기능은 돌아갔고, 가입-결제-분석-리포트 흐름도 연결되어 있었습니다. 하지만 실제로 결제를 받는 서비스로 전환하려면 그 사이를 메워야 할 항목들이 상당히 많았습니다.
이 글은 프로토타입에서 상용 서비스로 전환하면서 변경된 항목들을 카테고리별로 정리합니다. 각 항목마다 프로토타입 상태, 변경 내용, 변경 이유를 기록합니다. 구체적인 구현 코드나 비용 수치는 포함하지 않습니다. 무엇을 바꿨고, 왜 바꿨는지에 집중합니다.
변경 카테고리 전체 목록
| # | 카테고리 | 프로토타입 상태 | 상용화 후 상태 | 우선순위 |
|---|---|---|---|---|
| 1 | 보안 강화 | 기본 입력 제한만 존재 | 다층 검증 + 동시 실행 제한 | P0 |
| 2 | 인증 시스템 | 기본 세션 기반 | JWT + 토큰 갱신 | P0 |
| 3 | 다국어 지원 (i18n) | 영문 단일 | KO/EN 이중 언어 | P0 |
| 4 | 샘플 시스템 | 없음 | locale별 샘플 리포트 | P1 |
| 5 | 결제 연동 | 반자동 | webhook 자동화 + 크레딧 시스템 | P0 |
| 6 | 에러 처리 및 모니터링 | console.log 수준 | 구조화된 에러 + GA4 | P1 |
| 7 | DB 스키마 | 최소 테이블 구조 | 정규화 + 인덱싱 | P1 |
| 8 | 프론트엔드 호스팅 | 노코드 빌더 | Vercel 정적 호스팅 | P0 |
| 9 | DNS / SSL / 커스텀 도메인 | 빌더 기본 도메인 | 커스텀 도메인 + SSL | P0 |
| 10 | SEO / OG 이미지 | 없음 | Search Console + meta + OG | P1 |
graph LR
subgraph "프로토타입"
A[FE: 노코드 빌더] --> B[BE: 기본 API]
B --> C[DB: 최소 스키마]
B --> D[LLM API]
end
subgraph "상용화"
E[FE: Vercel + React] --> F[BE: FastAPI + JWT + Rate Limit]
F --> G[DB: 정규화 + 인덱스]
F --> H[LLM API]
F --> I[결제 Webhook]
F --> J[GA4 이벤트]
E --> K[i18n KO/EN]
E --> L[샘플 시스템]
E --> M[SEO + OG]
end
1. 보안 강화
프로토타입 상태
프론트엔드에서 입력 길이만 제한하고 있었습니다. 백엔드에는 별도의 검증 로직이 없었고, 요청 본문을 그대로 처리했습니다. 악의적 입력을 가정한 방어 코드는 존재하지 않았습니다. 해커톤 MVP에서는 이것으로 충분했습니다. 사용자가 심사위원 몇 명이고, 악의적 사용 동기가 없기 때문입니다.
변경 사항
보안 강화를 3개 계층으로 구분해서 적용했습니다.
계층 1: 프론트엔드 UX 검증
- 입력 형식 검증 (URL 패턴, 이메일 형식 등)
- 입력 길이 제한 (최소/최대)
- 특수문자 및 허용되지 않은 문자열 입력 시 즉시 안내 메시지
- 제출 버튼 비활성화 조건 명확화
프론트엔드 검증은 보안이 아니라 UX입니다. 사용자에게 “왜 이 입력이 안 되는지”를 설명하는 역할이고, 실제 방어는 백엔드에서 이루어집니다.
계층 2: 백엔드 allowlist 검증
- 허용된 값만 통과시키는 allowlist 기반 검증
- enum 타입 파라미터는 정의된 값 목록과 비교
- 문자열 파라미터는 패턴 매칭 후 통과 여부 결정
- 허용되지 않은 값은 요청 자체를 거부 (400 응답)
denylist(차단 목록) 대신 allowlist(허용 목록)를 선택한 이유가 있습니다. denylist는 “알려진 나쁜 입력”만 차단하므로 새로운 공격 패턴에 취약합니다. allowlist는 “알려진 좋은 입력”만 통과시키므로 기본적으로 안전합니다.
계층 3: 금칙 패턴 필터링
- 프롬프트 인젝션 시도 탐지 및 차단
- 시스템 프롬프트 탈취 시도 차단
- 의도하지 않은 특수 문자열 조합 필터링
- 금칙어 목록은 별도 설정 파일로 관리 (코드에 하드코딩하지 않음)
추가 방어: 동시 실행 제한
- 사용자당 동시 분석 요청 수 제한
- 제한 초과 시 대기열 안내 또는 요청 거부
- 크레딧 차감은 분석 성공 후에만 확정 (실패 시 복구)
| 계층 | 위치 | 역할 | 차단 시점 |
|---|---|---|---|
| 1 | 프론트엔드 | UX 안내 | 입력 시점 |
| 2 | 백엔드 진입부 | 허용값 검증 | 요청 수신 시 |
| 3 | 백엔드 처리부 | 금칙 패턴 탐지 | 처리 직전 |
| 추가 | 백엔드 | 동시 실행 제한 | 실행 시점 |
graph TD
A[사용자 입력] --> B{FE 형식 검증}
B -->|통과| C[API 요청]
B -->|실패| D[입력 오류 안내]
C --> E{BE allowlist 검증}
E -->|통과| F{금칙 패턴 필터}
E -->|실패| G[400 Bad Request]
F -->|통과| H{동시 실행 제한 확인}
F -->|실패| I[403 Forbidden]
H -->|여유| J[분석 실행]
H -->|초과| K[429 Too Many Requests]
J --> L{분석 성공?}
L -->|성공| M[크레딧 차감 확정]
L -->|실패| N[크레딧 복구]
변경 이유
결제가 붙는 순간 악의적 사용의 동기가 생깁니다. 무료 MVP에서는 누군가 이상한 입력을 넣어도 손해가 크지 않습니다. 유료 서비스에서는 크레딧 부정 사용, API 남용, 프롬프트 인젝션이 실제 비용으로 연결됩니다. LLM API 호출은 건당 비용이 발생하므로, 악의적 사용이 곧 운영 비용 증가입니다.
보안은 “나중에 하면 되는 것”이 아니라 “결제 기능이 붙는 순간 필수가 되는 것”이다.
2. 인증 시스템
프로토타입 상태
기본적인 세션 기반 인증을 사용하고 있었습니다. 로그인 후 세션이 유지되는 방식이었고, 토큰 만료나 갱신에 대한 고려가 없었습니다. CORS 설정도 개발 편의를 위해 전체 허용(*) 상태였습니다.
변경 사항
JWT 기반 인증으로 전환
- 액세스 토큰과 리프레시 토큰 분리
- 액세스 토큰에 짧은 만료 시간 설정
- 리프레시 토큰으로 자동 갱신
- 토큰 페이로드에 사용자 역할 및 권한 정보 포함
CORS 설정 강화
- 전체 허용(
*) 제거 - 허용 도메인을 명시적으로 지정 (프로덕션 도메인, 프리뷰 도메인)
- 허용 메서드와 헤더도 명시적으로 제한
- Credentials 옵션 활성화
Rate Limiting 적용
- IP 기반 + 사용자 기반 이중 제한
- 엔드포인트별 차등 제한 (인증 관련 엔드포인트는 더 엄격)
- 제한 초과 시 429 응답과 함께 재시도 가능 시점 안내
| 항목 | 프로토타입 | 상용화 |
|---|---|---|
| 인증 방식 | 세션 기반 | JWT (액세스 + 리프레시) |
| 토큰 만료 | 고려 안 함 | 단기 만료 + 자동 갱신 |
| CORS | * (전체 허용) | 도메인 명시 지정 |
| Rate Limit | 없음 | IP + 사용자 이중 제한 |
| 권한 관리 | 없음 | 토큰 페이로드에 역할 포함 |
변경 이유
세션 기반 인증은 단일 서버 환경에서는 단순하지만, 프론트엔드와 백엔드가 다른 도메인에서 운영되는 SaaS 구조에서는 한계가 있습니다. JWT는 stateless하므로 서버 확장에도 유리하고, 프론트엔드에서 토큰을 직접 관리할 수 있어 SPA 구조에 적합합니다. CORS와 Rate Limiting은 API를 외부에 노출하는 서비스에서 기본 방어선입니다.
3. 다국어 지원 (i18n)
프로토타입 상태
영문 단일 언어였습니다. 모든 UI 텍스트가 컴포넌트 코드에 직접 작성되어 있었습니다. 버튼 텍스트, 안내 메시지, 에러 메시지가 영어 문자열로 하드코딩된 상태였습니다.
변경 사항
프론트엔드 변경
- UI 텍스트를 코드에서 분리하여 locale별 JSON 파일로 관리
- 언어 전환 UI 추가 (헤더에 KO/EN 토글)
- 브라우저 locale 감지 후 자동 언어 설정
- 수동 전환 시 선택 언어를 로컬 스토리지에 저장
- 날짜, 숫자 포맷도 locale에 맞게 변환
- 링크, 라우팅 경로에 locale prefix 적용
백엔드 변경
- 분석 리포트 생성 시 요청 locale에 따라 프롬프트 분기
- LLM에 전달하는 시스템 프롬프트에 응답 언어 지시 포함
- API 응답의 에러 메시지도 locale별 분기
- 이메일 알림 템플릿 locale 분리
번역 관리
- 번역 키 네이밍 규칙 수립 (예:
page.analysis.button.start) - 누락 키 발생 시 fallback 언어(영어) 표시
- 번역 파일 업데이트 체크리스트 운영
| 변경 위치 | 프로토타입 | 상용화 |
|---|---|---|
| UI 텍스트 | 코드에 하드코딩 | locale별 JSON 분리 |
| 리포트 본문 | 영문 고정 | 요청 locale에 따라 분기 |
| 에러 메시지 | 영문 고정 | locale별 분기 |
| 날짜/숫자 | 미국식 고정 | locale 기반 포맷 |
| 라우팅 | 단일 | locale prefix |
| 감지 | 없음 | 브라우저 locale 자동 감지 |
변경 이유
WICHI는 글로벌 SaaS입니다. 한국 시장 진입과 글로벌 확장을 동시에 하려면 i18n은 선택이 아니라 필수입니다.
i18n을 나중에 넣으면 전체 UI 컴포넌트를 다시 뜯어야 합니다. 하드코딩된 문자열을 하나씩 찾아서 번역 키로 교체하는 작업은, 컴포넌트 수가 늘어난 뒤에 할수록 비용이 기하급수적으로 증가합니다. 상용화 시점에 구조를 잡는 것이 가장 효율적이었습니다.
i18n은 “번역”이 아니라 “구조”의 문제다. 텍스트를 코드에서 분리하는 것이 핵심이고, 실제 번역은 그 이후의 작업이다.
특히 백엔드 i18n은 프론트엔드보다 고려 사항이 많습니다. LLM 기반 서비스에서는 분석 결과물 자체의 언어가 바뀌어야 하므로, 프롬프트 레벨에서의 언어 지시가 필요합니다. 단순히 UI를 번역하는 것과는 다른 차원의 작업입니다.
4. 샘플 시스템
프로토타입 상태
샘플 데이터가 없었습니다. 가입 후 크레딧을 소비해야만 분석 결과를 볼 수 있었습니다. “이 서비스가 어떤 결과를 주는지”를 확인하려면 결제가 선행되어야 하는 구조였습니다.
변경 사항
샘플 리포트 시스템 구축
- 비로그인 상태에서 접근 가능한 샘플 리포트 제공
- 영문 샘플: 글로벌 DTC 브랜드 기반
- 한국어 샘플: 국내 대기업 브랜드 기반
- 샘플 리포트는 실제 분석 결과와 동일한 UI로 렌더링
전환 유도 설계
- 샘플 리포트 끝에 가입 유도 게이트 배치
- 전체 리포트의 일정 비율까지만 무료 열람 가능
- 가입 게이트 문구도 locale에 따라 분기
- 샘플에서 가입으로의 전환 이벤트 GA4 추적
샘플 선정 기준
- 해당 locale의 사용자가 인지도가 높은 브랜드 선택
- 분석 결과가 풍부하게 나오는 브랜드 선택 (빈약한 결과는 역효과)
- 주기적으로 샘플 데이터 갱신 (오래된 데이터는 신뢰도 저하)
| 항목 | 프로토타입 | 상용화 |
|---|---|---|
| 결과물 미리보기 | 불가 | 샘플 리포트로 가능 |
| 비로그인 접근 | 불가 | 샘플 한정 가능 |
| locale별 콘텐츠 | 해당 없음 | 각 locale에 맞는 브랜드 |
| 전환 유도 | 없음 | 가입 게이트 + GA4 이벤트 |
변경 이유
“돈을 내기 전에 결과물을 확인하고 싶다”는 자연스러운 심리입니다. 특히 SaaS에서 “분석 리포트”라는 결과물은 텍스트 기반이므로, 결과물의 품질 편차가 클 수 있다는 인식이 있습니다. 샘플 없이 결제부터 요구하면 전환율이 나올 수 없습니다.
locale별 브랜드를 샘플로 제공한 이유도 명확합니다. 한국 사용자에게 미국 브랜드 샘플을 보여주면 “이게 우리 시장에도 적용되나?”라는 의문이 남습니다. 각 시장의 사용자가 직접 아는 브랜드의 분석 결과를 보여줘야 결과물의 가치를 체감할 수 있습니다.
샘플은 “데모”가 아니라 “증거”다. 사용자에게 “이 서비스가 돈을 낼 만한 결과를 제공한다”는 것을 입증하는 장치다.
5. 결제 연동
프로토타입 상태
결제 페이지는 존재했지만 webhook 처리가 불완전했습니다. 결제 성공 이벤트를 수신하는 코드는 있었으나, 에지 케이스(중복 이벤트, 실패 후 재시도, 환불 등)에 대한 처리가 없었습니다. 크레딧 부여는 수동으로 이루어졌습니다.
변경 사항
Webhook 자동화
- 결제 플랫폼에서 발송하는 webhook 이벤트 수신 및 서명 검증
- 이벤트 타입별 핸들러 분기 (결제 완료, 환불, 구독 변경 등)
- 멱등성(idempotency) 보장 — 동일 이벤트를 중복 수신해도 한 번만 처리
- webhook 수신 실패 시 재시도 대응 (재시도 시에도 중복 처리 방지)
- 수신된 모든 webhook 이벤트 로깅 (디버깅 및 감사 추적용)
크레딧 시스템 설계
- 결제 완료 → 크레딧 충전 → 분석 실행 시 차감의 3단계 흐름
- 크레딧 잔액 조회 API
- 크레딧 차감은 분석 시작 시 예약, 완료 시 확정
- 분석 실패 시 예약된 크레딧 자동 복구
- 크레딧 사용 이력 조회 (충전, 차감, 복구 내역)
주문 상태 관리
- 주문 상태 머신: 결제 대기 → 결제 완료 → 크레딧 부여 → (환불 시) 크레딧 회수
- 각 상태 전이에 타임스탬프 기록
- 비정상 상태 전이 발생 시 알림
stateDiagram-v2
[*] --> 결제대기: 결제 페이지 진입
결제대기 --> 결제완료: webhook 수신 (결제 성공)
결제대기 --> 결제실패: webhook 수신 (결제 실패)
결제완료 --> 크레딧부여: 크레딧 충전 처리
크레딧부여 --> [*]
결제완료 --> 환불요청: 사용자 환불 요청
환불요청 --> 크레딧회수: 잔여 크레딧 확인 후 회수
크레딧회수 --> 환불완료: 환불 처리 완료
환불완료 --> [*]
결제실패 --> [*]
방어 로직
- 결제 금액과 상품 정보의 서버사이드 재검증 (클라이언트 전달값 신뢰하지 않음)
- 동일 사용자의 짧은 간격 중복 결제 탐지 및 차단
- 크레딧 잔액이 마이너스가 되는 것을 방지하는 제약 조건
변경 이유
MVP에서는 “결제가 되긴 한다” 수준이면 충분했습니다. 상용 서비스에서는 다릅니다. 결제 후 크레딧이 즉시 반영되지 않으면 사용자는 “돈만 빠져나가고 서비스를 못 쓰는” 상황을 경험합니다. 중복 차감이 발생하면 신뢰를 잃습니다.
Webhook 기반 자동화와 트랜잭션 안전성은 유료 서비스의 기본 요건입니다. 특히 크레딧 차감의 예약-확정 패턴은, 분석이 LLM API 호출에 의존하므로 실패 가능성이 항상 존재한다는 점에서 필수적이었습니다.
결제 시스템에서 “대부분의 경우 잘 작동한다”는 충분하지 않다. 엣지 케이스 하나가 사용자의 돈과 직결된다.
6. 에러 처리 및 모니터링
프로토타입 상태
에러 처리는 console.log 수준이었습니다. 에러가 발생하면 브라우저 콘솔에 찍히거나, 백엔드 로그에 스택 트레이스가 남는 정도였습니다. 사용자에게 보여주는 에러 메시지는 “Something went wrong” 수준의 범용 메시지였습니다. 사용자 행동 데이터 수집은 하지 않았습니다.
변경 사항
프론트엔드 에러 처리
- 에러 바운더리(Error Boundary) 적용 — 컴포넌트 에러가 전체 앱을 크래시시키지 않도록 격리
- API 요청 실패 시 사용자 친화적 메시지 표시 (locale별 분기)
- 네트워크 에러와 서버 에러 구분 안내
- 재시도 가능한 에러에는 재시도 버튼 제공
백엔드 에러 처리
- 구조화된 에러 응답 포맷 통일 (
code,message,detail필드) - HTTP 상태 코드 정확한 사용 (이전에는 대부분 500을 반환)
- 에러 레벨 분류: 사용자 입력 오류(4xx) vs 서버 오류(5xx) 명확 구분
- LLM API 호출 실패 시 재시도 로직 (지수 백오프)
- 재시도 횟수 소진 시 분석 실패 처리 + 크레딧 복구
모니터링 — GA4 연동
- 주요 전환 이벤트 정의 및 추적:
| 이벤트 | 시점 | 목적 |
|---|---|---|
sign_up | 회원가입 완료 | 가입 전환율 측정 |
login | 로그인 | 재방문 패턴 파악 |
view_sample | 샘플 리포트 조회 | 샘플의 효과 측정 |
begin_checkout | 결제 페이지 진입 | 결제 퍼널 시작점 |
purchase | 결제 완료 | 결제 전환율 측정 |
begin_analysis | 분석 시작 | 크레딧 사용 패턴 |
analysis_complete | 분석 완료 | 분석 성공률 |
analysis_failed | 분석 실패 | 실패율 모니터링 |
로깅 구조화
- 요청 ID 기반 추적 — 하나의 요청이 여러 서비스를 거쳐도 추적 가능
- 로그 레벨 체계화 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- 민감 정보(토큰, 비밀번호, API 키)는 로그에서 마스킹
변경 이유
데이터 없이는 개선할 수 없습니다. 어디서 이탈하는지, 결제 전환율이 얼마인지, 분석을 시작한 사용자 중 완료까지 가는 비율이 얼마인지를 알아야 다음 우선순위를 정할 수 있습니다.
에러 처리도 마찬가지입니다. “Something went wrong”이라는 메시지는 사용자에게도, 개발자에게도 쓸모가 없습니다. 사용자에게는 “무엇이 잘못되었고, 어떻게 해야 하는지”를 알려줘야 하고, 개발자에게는 “어디서, 왜 실패했는지”가 구조화된 형태로 기록되어야 합니다.
7. DB 스키마 진화
프로토타입 상태
최소한의 테이블만 존재했습니다. 사용자 테이블, 분석 결과 테이블 정도였고, 정규화가 이루어지지 않은 상태였습니다. 인덱스는 기본 키 외에 없었고, 쿼리 성능에 대한 고려가 없었습니다.
변경 사항
테이블 추가 및 정규화
- 크레딧 트랜잭션 테이블 신규 (충전, 차감, 복구 이력)
- 주문 테이블 신규 (결제 상태 머신)
- 샘플 리포트 테이블 신규
- 사용자 설정 테이블 분리 (locale, 알림 설정 등)
- 분석 결과 테이블 구조 변경 (locale 컬럼 추가, 메타데이터 정규화)
인덱싱
- 빈번한 조회 패턴에 맞는 인덱스 추가
- 크레딧 잔액 조회 최적화 (사용자 ID + 생성일 복합 인덱스)
- 분석 결과 목록 조회 최적화 (사용자 ID + 상태 + 생성일)
- 불필요한 전체 테이블 스캔 제거
데이터 무결성
- 외래 키 제약 조건 추가
- 크레딧 잔액 마이너스 방지 CHECK 제약
- 상태 전이에 대한 트리거 또는 애플리케이션 레벨 검증
- 소프트 삭제(soft delete) 패턴 적용 (실수로 데이터 유실 방지)
| 항목 | 프로토타입 | 상용화 |
|---|---|---|
| 테이블 수 | 2–3개 | 7개 이상 |
| 인덱스 | PK만 | 조회 패턴 기반 복합 인덱스 |
| 외래 키 | 없음 | 모든 관계에 적용 |
| 데이터 무결성 | 애플리케이션 레벨만 | DB 레벨 제약 + 앱 레벨 검증 |
| 삭제 방식 | 하드 삭제 | 소프트 삭제 |
변경 이유
프로토타입에서는 “데이터가 저장되면 된다”가 목표였습니다. 상용 서비스에서는 “데이터가 정확하고, 빠르게 조회되고, 실수로 유실되지 않아야 한다”가 목표입니다.
특히 크레딧 시스템이 추가되면서 트랜잭션 정합성이 중요해졌습니다. 크레딧 차감과 분석 실행이 원자적으로 처리되지 않으면, 크레딧은 차감되었는데 분석은 실패하는 상황이 발생합니다. DB 레벨의 제약 조건은 이런 상황을 방어하는 마지막 안전장치입니다.
8. 프론트엔드 호스팅
프로토타입 상태
노코드 빌더 기반 호스팅을 사용했습니다. 빠르게 UI를 만들 수 있어서 해커톤 기간에 유용했지만, 상용화 단계에서는 커스터마이징과 비용 통제에 한계가 있었습니다.
변경 사항
- 노코드 빌더에서 코드 기반 프론트엔드(React + Vite)로 완전 이전
- Vercel 기반 정적 호스팅으로 전환
- Git push 기반 자동 배포 파이프라인 구축
- PR마다 Preview Deploy 자동 생성
- 이전 배포 버전으로 즉시 롤백 가능
- 환경변수를 Production / Preview / Development로 분리 관리
- 빌더 전용 의존성 (빌더 플러그인, 태거 등) 완전 제거
| 항목 | 노코드 빌더 | Vercel |
|---|---|---|
| 배포 트리거 | 에디터에서 수동 | git push → 자동 |
| PR 프리뷰 | 불가 | PR당 Preview Deploy |
| 롤백 | 수동 복원 | 즉시 롤백 |
| 환경변수 | 제한적 | 환경별 분리 |
| 코드 제어 | 부분적 | 완전 |
| 월 비용 | 유료 구독 | 무료 티어 |
변경 이유
노코드 빌더의 월 구독 비용 대비 Vercel 무료 티어가 경제적이었고, 프론트엔드 코드를 직접 제어해야 i18n, SEO, 커스텀 컴포넌트 등 상용화에 필요한 기능을 자유롭게 구현할 수 있었습니다. 프로토타이핑 도구와 프로덕션 호스팅은 역할이 다릅니다.
9. DNS / SSL / 커스텀 도메인
프로토타입 상태
노코드 빌더가 제공하는 기본 서브도메인을 사용했습니다. SSL은 빌더가 자동 제공. 커스텀 도메인은 연결되어 있지 않았습니다.
변경 사항
커스텀 도메인 설정
- 프로덕션 도메인 구매 및 등록
- Cloudflare DNS에서 네임서버 레코드 설정
- 프론트엔드(Vercel)와 백엔드(Railway) 각각에 커스텀 도메인 연결
- www와 루트 도메인 리다이렉트 설정
SSL 인증서
- Vercel 측: 자동 SSL 프로비저닝 (Let’s Encrypt)
- 백엔드 측: Railway 자동 SSL
- 인증서 갱신은 양쪽 모두 자동 관리
DNS 레코드 정리
- A 레코드, CNAME 레코드 정확한 설정
- 불필요한 레거시 레코드 제거
- DNS 전파 확인 후 이전 도메인에서 리다이렉트 설정
| 항목 | 프로토타입 | 상용화 |
|---|---|---|
| 도메인 | 빌더 서브도메인 | 커스텀 도메인 |
| SSL | 빌더 자동 제공 | 양쪽 자동 프로비저닝 |
| DNS 관리 | 해당 없음 | Cloudflare |
| www 리다이렉트 | 해당 없음 | 설정 완료 |
변경 이유
something.builder-name.app 같은 서브도메인은 프로토타입에서는 문제없지만, 유료 서비스에서는 신뢰도에 영향을 줍니다. 커스텀 도메인은 브랜드 정체성의 기본이고, SSL은 사용자 데이터 보호의 기본입니다. 결제 정보를 다루는 서비스에서 이 둘은 협상의 대상이 아닙니다.
10. SEO / OG 이미지
프로토타입 상태
SEO 대응이 전무했습니다. robots.txt도 없고, sitemap.xml도 없었습니다. 메타 태그는 기본값 그대로였고, Open Graph 이미지는 설정되지 않았습니다. 검색엔진에 등록된 적이 없었습니다.
변경 사항
검색엔진 등록
- Google Search Console에 도메인 속성 등록
- DNS TXT 레코드로 소유권 인증
sitemap.xml자동 생성 및 제출robots.txt설정 (크롤링 허용 경로 명시)
메타 태그 정비
- 페이지별
title,description최적화 - 구조화된 데이터(JSON-LD) 추가 (SoftwareApplication 스키마)
- canonical URL 설정 (중복 콘텐츠 방지)
- locale별
hreflang태그 (다국어 검색 노출)
Open Graph 이미지
- 서비스 대표 OG 이미지 제작
- 주요 페이지별 OG 이미지 분기
- OG 이미지를 프로젝트 내부에 저장 (외부 CDN 의존 제거)
- Twitter Card 메타 태그도 함께 설정
기술 SEO
- 페이지 로딩 속도 최적화 (정적 호스팅 전환의 부수 효과)
- 모바일 반응형 확인
- Core Web Vitals 기본 수준 확보
graph TD
A[검색엔진 크롤러] --> B[robots.txt 확인]
B --> C[sitemap.xml 파싱]
C --> D[페이지 크롤링]
D --> E[메타 태그 수집]
D --> F[구조화 데이터 수집]
D --> G[hreflang 확인]
E --> H[검색 결과 표시]
F --> H
G --> I[locale별 검색 결과 분기]
J[소셜 공유] --> K[OG 메타 태그 파싱]
K --> L[OG 이미지 + 제목 + 설명 표시]
변경 이유
유료 SaaS에 사용자가 유입되려면 검색에서 발견되어야 합니다. 광고 없이 시작하는 초기 SaaS에서 오가닉 검색은 사실상 유일한 무료 유입 채널입니다. SEO가 없으면 아무리 좋은 서비스를 만들어도 존재 자체를 모릅니다.
OG 이미지는 소셜 공유 시 첫인상을 결정합니다. 링크를 공유했을 때 제목과 설명, 이미지가 깔끔하게 나오는 것과 아무것도 나오지 않는 것은 클릭률에 차이를 만듭니다.
우선순위 판단 기준
10개 카테고리를 동시에 진행할 수 없으므로, 우선순위를 정해야 했습니다. 다음 기준으로 P0/P1을 구분했습니다.
P0 (상용화 전 필수 — 결제를 받기 위한 최소 요건)
- 보안 강화: 결제가 있으면 보안은 선택이 아님
- 인증 시스템: JWT 없이 API를 보호할 수 없음
- 결제 연동: 수동 크레딧 부여로는 서비스 운영 불가
- 프론트엔드 호스팅: 코드 제어권 없이 i18n/SEO 구현 불가
- DNS/SSL/도메인: 커스텀 도메인 없이 결제 서비스 운영 불가
- i18n: 한국 시장 진입에 필수, 나중에 넣으면 비용 급증
P1 (상용화 직후 필수 — 성장을 위한 요건)
- 샘플 시스템: 전환율에 직접 영향
- 에러 처리/모니터링: 데이터 없이 개선 불가
- DB 스키마 정비: 데이터 정합성과 성능
- SEO/OG 이미지: 유입 채널 확보
graph TD
subgraph "P0: 결제 전 필수"
A[보안 강화] --> B[인증 시스템]
B --> C[결제 연동]
D[FE 호스팅 전환] --> E[DNS/SSL/도메인]
D --> F[i18n]
end
subgraph "P1: 성장 필수"
G[샘플 시스템]
H[에러 처리/모니터링]
I[DB 스키마 정비]
J[SEO/OG 이미지]
end
C --> G
F --> G
E --> J
B --> H
C --> I
| 우선순위 | 기준 | 해당 카테고리 |
|---|---|---|
| P0 | 이것 없이 결제를 받을 수 없다 | 보안, 인증, 결제, 호스팅, DNS/SSL, i18n |
| P1 | 이것 없이 성장할 수 없다 | 샘플, 모니터링, DB 정비, SEO |
| P2 | 있으면 좋지만 없어도 런칭 가능 | (이 글에서 다루지 않는 항목들) |
전체 요약
| # | 영역 | 프로토타입 | 상용화 | 우선순위 |
|---|---|---|---|---|
| 1 | 보안 | 기본 입력 제한 | 3계층 검증 + 동시 실행 제한 + 트랜잭션 안전 | P0 |
| 2 | 인증 | 세션 기반 | JWT + CORS + Rate Limit | P0 |
| 3 | i18n | 영문 단일 | KO/EN 이중 언어 (FE + BE) | P0 |
| 4 | 샘플 | 없음 | locale별 샘플 리포트 + 가입 게이트 | P1 |
| 5 | 결제 | 반자동 | webhook 자동화 + 크레딧 시스템 | P0 |
| 6 | 에러/모니터링 | console.log | 구조화 에러 + GA4 8개 이벤트 | P1 |
| 7 | DB | 최소 테이블 | 정규화 + 인덱싱 + 무결성 제약 | P1 |
| 8 | 호스팅 | 노코드 빌더 | Vercel 정적 호스팅 | P0 |
| 9 | DNS/SSL | 빌더 서브도메인 | 커스텀 도메인 + 자동 SSL | P0 |
| 10 | SEO | 없음 | Search Console + sitemap + OG 이미지 | P1 |
회고
이 10개 항목은 MVP에서 “빠졌던 것”이 아닙니다. 프로토타입 단계에서는 필요하지 않았던 것들입니다.
프로토타입의 목적은 핵심 가치를 검증하는 것입니다. “이 분석이 유용한가?”라는 질문에 대한 답을 얻는 것이 전부였고, 그 목적에 비추면 MVP는 충분했습니다.
상용화의 목적은 그 가치를 안정적으로, 반복적으로, 신뢰할 수 있게 전달하는 것입니다. 위 10개 항목은 모두 후자에 해당합니다.
프로토타입은 “이게 가능한가?”에 답하고, 상용 서비스는 “이걸 믿을 수 있는가?”에 답한다.
전환 과정에서 관찰한 패턴을 정리합니다.
- 결제가 붙는 순간 모든 것의 기준이 바뀐다. 보안, 에러 처리, 데이터 무결성 — 무료일 때는 “있으면 좋은 것”이었던 항목들이 유료일 때는 “없으면 안 되는 것”이 됩니다.
- i18n은 구조의 문제다. 번역이 아니라 텍스트 분리입니다. 컴포넌트 수가 적을 때 잡아야 합니다.
- 샘플은 마케팅이 아니라 신뢰 장치다. “이 서비스가 무슨 결과를 주는지” 보여주는 것은 판매 전략이 아니라 사용자에 대한 기본 예의입니다.
- 모니터링 없이 개선은 감에 의존한다. 데이터가 있어야 우선순위를 정할 수 있습니다.
- DB 제약 조건은 마지막 방어선이다. 애플리케이션 코드에 버그가 있어도, DB 레벨 제약이 데이터 무결성을 지켜줍니다.
이 글에서 다루지 않은 항목들(블로그, 이메일 마케팅, A/B 테스트, 고급 분석 기능 등)은 이후 별도로 정리할 예정입니다.
관련 글

조코딩 해커톤 참가 기록 — 3일간 GEO SaaS 만들기
조코딩 해커톤에서 3일 만에 WICHI MVP를 구축한 과정과 기술 스택(FastAPI, React, Supabase) 선택 이유, 그리고 제한된 시간 내 '작동하는 제품'을 만드는 우선순위 기록

GEO 사업 기회 6가지와 WICHI의 선택
AI 검색(GEO) 시장의 3가지 기회 요인과 WICHI가 '광고'나 '대행'이 아닌 'SaaS형 모니터링'을 첫 번째 비즈니스 모델로 선택한 전략적 배경 분석

해커톤 탈락 후 — 독립 SaaS로 전환한 과정
해커톤 탈락 직후 24시간 내에 WICHI를 독립 SaaS로 피벗하며 결정한 i18n 도입, SEO 설정, 수익화 로드맵 재구성 등 실행 항목과 의사결정 기록