바이브 코딩 PRD와 TRD
최근 IT 업계와 1인 창업 생태계에서 가장 뜨거운 화두는 단연 **'바이브 코딩(Vibe Coding)'**입니다. 코드를 직접 한 줄 한 줄 타이핑하는 대신, AI에게 내 비전과 목적을 전달하여 코드가 스스로 생성되도록 통제하는 개발 방식이죠.
하지만 많은 이들이 이 놀라운 도구를 쥐고도 실패를 경험합니다. "AI한테 홈페이지를 만들어 달라고 했는데, 결과물이 완전히 엉뚱하게 나와요"라는 탄식이 도처에서 들립니다.
이유는 단순합니다. **"입력 품질이 출력 품질을 결정한다(Garbage In, Garbage Out)"**는 원칙 때문입니다. AI 코딩 시대에 기획의 가치는 소멸한 것이 아니라, 오히려 성공과 실패를 가르는 절대적인 열쇠로 부각되고 있습니다.
이번 글에서는 서비스 기획부터 배포까지 AI를 완벽하게 지휘하기 위한 **'살아있는 PRD(제품 요구사항 정의서) 작성법'**과 실무 팁을 정리해 드립니다.
1. AI 시대, PM의 역할은 어떻게 변하고 있는가?
과거의 프로덕트 매니저(PM)나 서비스 기획자들은 전체 업무 시간의 30~50%를 문서 작성과 포맷팅, 테이블 정리에 소모해야 했습니다. 정작 사용자들의 진짜 아픔(Pain Point)을 집요하게 파고들고 제품의 비즈니스 가치를 검증하는 '프로덕트 디스커버리(Product Discovery)'에 쓸 시간이 턱없이 부족했던 것이죠.
그러나 AI native PRD 도구의 등장으로 문서 작성 속도는 비약적으로 빨라졌습니다. 하루가 꼬박 걸리던 Boilerplate 작업이 단 한 시간, 혹은 몇 분 만에 완수되는 혁신이 일어났습니다. 이러한 인지적 부하 완화(Cognitive Offloading) 덕분에 PM의 본질적인 정체성이 바뀌고 있습니다.
- 단순 관리자에서 '제품 제작자(Product Maker)'로: PM은 이제 AI라는 강력한 조수를 활용해 프로토타입 제작 등 기술적 구현 영역까지 아우르는 실질적인 빌더가 됩니다.
- 전략과 질문을 설계하는 '오케스트라 지휘자': AI는 수십 년간 쌓인 전 세계의 지식과 데이터를 기계적으로 조합하고 표현하는 데 천재적입니다. 하지만 "우리가 어떤 문제를 왜 풀어야 하는가?"라는 본질적인 질문을 던지고 올바른 방향을 잡는 것은 오직 인간의 몫입니다. 따라서 최고의 PM은 **PRD를 직접 타이핑하는 사람이 아니라, AI가 올바른 답을 내놓도록 '질문을 설계하는 사람'**이 되어야 합니다.
2. "잘 쓰인 PRD"와 "AI에게 잘 먹히는 PRD"는 다르다
우리가 사람과 협업할 때 잘 썼다고 칭찬받는 매끄럽고 유려한 문장들은 AI 코딩 도구(Cursor, Claude Code 등) 앞에서는 무력해지기 일쑤입니다. AI는 모호한 요구사항을 만나면 사람처럼 질문하는 것이 아니라, 스스로 수많은 가정을 덧붙여 임의대로 설계하고 코드를 짜버리기 때문입니다.
따라서 AI가 스스로 추측하고 망상할 여지를 0으로 만드는 것이 AI-Ready PRD의 핵심입니다.
① 자의적 해석을 낳는 '6대 기능 단어'를 해체하라
다음 6가지 단어는 문서에 단 한 줄로 쓰이지만, 개발자나 AI 입장에서는 수십 가지의 설계 분기를 낳는 '시한폭탄' 같은 단어들입니다. PRD에 이 단어들이 보인다면 반드시 동사 단위로 쪼개어 구체적인 화면, 조건, 데이터를 서술해야 합니다.
- 추천: 어떤 데이터를 기반으로 정렬할 것인가? 데이터가 없거나 첫 방문자라면 어떤 기본 화면을 보여줄 것인가?
- 결제: 단건 결제인가, 정기 구독인가? 쿠폰, 포인트, 부분 취소, 환불 정책은 어떻게 처리할 것인가?
- 채팅: 실시간 웹소켓 기반인가, 비동기식 문의 메시지인가? 이미지 전송과 읽음 표시는 필수인가?
- 관리자: 단순 조회용 대시보드인가, 데이터 수정/승인 및 관리자 권한 제어까지 포함하는가?
- 검색: 텍스트 매칭 위주의 단순 키워드 검색인가, 자동완성과 다중 필터/정렬이 작동해야 하는가?
- 위치 기반: 사용자의 물리적 위경도 권한 획득 실패 시 어떤 대체 흐름(Fallback UI)을 제공할 것인가?
② 명확하게 "Out of Scope (제외할 범위)"를 명시하라
"이번 릴리즈 단계에서 개발하지 않을 것"을 명문화하는 작업은 AI 코딩 도구의 토큰을 아끼고 비용을 절감하는 가장 강력한 안전장치입니다. 제외 범위를 쳐주지 않으면 AI는 자기 멋대로 세부 아키텍처를 과도하게 확장하여 클라우드 비용을 폭발시킵니다. "1차 MVP 버전에서는 정기 결제와 AI 추천 알고리즘을 배제하고 단건 결제와 수동 큐레이션으로 한정한다"처럼 물리적인 경계를 명확히 그어주어야 합니다.
3. 실제 사례로 보는 PRD 고도화 비하인드: 서울시 따릉이 개선 프로젝트
이해를 돕기 위해 실제 서울특별시의 공공자전거 서비스인 '따릉이 개선 프로젝트'의 PRD 사례를 살펴보겠습니다.
- 문제 정의(Problem): 최근 따릉이 이용자의 70% 이상이 2030 세대인 반면, 5060 시니어 사용자는 10% 미만에 불과했습니다. 시니어층 심층 유저 인터뷰 결과, 이들은 "모바일 결제 단계"에서 카드 번호 입력, 본인 인증 등 복잡한 절차에 직면해 결제를 포기하고 이탈한다는 치명적인 페인포인트를 발견했습니다.
- 가설 설정(Hypothesis): 기존의 '가족 등록(만 13세 미만 자녀 대신 부모가 결제하는 기능)' 구조를 역으로 확장하여, "자녀가 시니어 부모님을 패밀리 멤버로 등록해 이용권을 미리 대리 결제해 줄 수 있다면" 시니어의 결제 장벽을 완전히 허물 수 있을 것이라는 가설을 세웠습니다.
- AI용 스펙 명세 (Before & After):
- Before (모호한 기획): "자녀가 부모님 대신 가족권을 선물하고 부모님은 쉽게 대여할 수 있게 한다."
- After (AI-Ready 구체 기획): "자녀가 본인 앱 내 '가족등록관리' 메뉴에서 부모님의 휴대폰 번호를 입력하면 연동 링크가 공유된다. 부모님은 앱 설치 없이 카카오톡 링크를 터치해 진입하여 자녀가 미리 결제한 대여권을 즉시 활성화하고, 대여소에서 물리 자전거의 QR 코드를 스캔해 대여를 완료한다. 1차 범위에서는 부모님이 자녀에게 이용권을 직접 '조르기'하는 역방향 푸시 기능은 제외한다."
이처럼 문제의 인과관계와 기능의 명확한 경계(Scope)를 PRD에 구체적으로 명시해 주었을 때, AI 코딩 에이전트는 기획자의 의도와 95% 이상 일치하는 완벽한 웹앱 코드를 단 한 번의 에러 없이 빌드해 낼 수 있습니다.
4. 지속 가능한 성공을 만드는 "살아있는 문서(Living Document)"
훌륭한 기획자들은 PRD를 '기획 단계에서 한 번 쓰고 서랍 속에 넣어두는 문서'로 취급하지 않습니다. 프로젝트의 라이프사이클 전체를 함께 관통하며 끊임없이 개정되는 **'살아있는 문서(Living Document)'**로 운영하죠.
- Why & Goals 로 시작해 기획의 흔들리지 않는 북극성을 세웁니다.
- MoSCoW 우선순위를 적용해 MVP 기능만 빠르게 추려 AI 에이전트로 초고속 빌드를 실행합니다.
- 실제 배포(Ship) 후 사용자 정량 로그(전환율, 이탈률 등)와 정성적 데이터(세션 리플레이, 클릭 무반응 패턴 등)를 모니터링합니다.
- 도출된 분석 결과를 다시 PRD의 '정량/정성적 근거' 섹션에 업데이트하고, 2차 고도화 피드백 루프를 돌리는 플라이휠을 완성합니다.
코딩 도구의 스펙이 아무리 현란하게 진화하더라도, 결국 **"우리가 이 서비스를 왜 만들며, 누구의 문제를 해결할 것인가?"**에 대한 인간의 끈질긴 탐구와 논리적 약속이 담긴 문서가 없다면 그 프로젝트는 반드시 좌초합니다.
다가오는 AI 시대의 생존자가 되고 싶으시다면, 코딩 공부 이전에 **'스스로 생각하고 질문을 설계하며, 문제를 벼려내는 PRD 작성의 기술'**부터 탄탄하게 장착해 보시는 건 어떨까요?


🚀 AI-Ready 1-Page PRD 실무 템플릿 & 가이드라인
문서 상태: 초안(Draft) / 리뷰 중(In Review) / 확정(Approved)
버전: v1.0
작성자: [이름/역할]
최종 수정일: 2026-07-06
📌 [템플릿 활용 가이드] AI 코딩 도구의 효율을 극대화하는 법
Cursor, Claude Code 등 AI 코딩 에이전트와 협업할 때 **가장 나쁜 문서는 '모호한 한 줄짜리 요구사항'**입니다. AI는 모호한 틈을 스스로 상상해 채워 넣기 때문에 구현 의도와 다른 스파게티 코드를 생성하기 쉽습니다.
본 템플릿은 **"AI가 스스로 추측할 여지를 0으로 만든다"**는 목적 하에, 현업 PM 및 개발자들의 실전 노하우를 결합하여 설계되었습니다.
1. 개요 및 문제 정의 (Why)
이 제품/기능을 왜 만드는지 명확한 데이터와 맥락을 제공하여 AI가 비즈니스 목적을 이해하도록 돕습니다.
- 해결하려는 문제: (예: 결제 실패율이 8%이며, 그 중 60%가 카드 한도 초과로 발생함)
- 기획 배경 및 데이터 근거: (예: 결제 실패 후 다른 카드로 즉시 재시도할 수 있는 경로가 없어 이탈율이 높음)
- 비즈니스 목표: (예: 결제 실패 후 재시도 성공률 30% 이상 달성, 결제 단계 이탈율 15% 감소)
2. 사용자 스토리 및 핵심 여정 (Who & When)
사용자의 관점에서 '누가, 언제, 무엇을, 왜' 원하는지 시나리오 기반으로 상세히 기술합니다.
- 핵심 페르소나: (예: 바쁜 일상 중에 빠르고 간편한 결제를 원하는 30대 회사원)
- 사용자 스토리 (User Story Format):As a (사용자 유형) 쇼핑을 자주 하는 구매자로서,
I want to (원하는 행동) 결제 실패 시 다른 카드로 즉시 재시도하여,
So that (얻고자 하는 가치) 장바구니로 돌아가 처음부터 결제 단계를 다시 밟는 번거로움 없이 결제를 완료하고 싶다. - Happy Path (성공 경로):
- 사용자가 상품 상세 페이지에서 결제 버튼을 누름.
- 결제 API 호출 중 한도 초과 오류 발생.
- 시스템이 실패 사유와 함께 "다른 카드로 결제" 버튼을 화면에 즉시 노출함.
- 사용자가 버튼을 눌러 등록된 카드 목록 모달을 띄우고 다른 카드를 선택함.
- 결제 성공 후 완료 화면으로 정상 이동함.
3. 핵심 기능 정의 (What)
추상적인 단어를 배제하고, 화면/조건/데이터 분기 기준으로 동사 단위로 명확히 쪼개어 기술합니다.
⚠️ 주의해야 할 'AI 오해 유발' 6대 단어 명세 가이드
AI에게 아래 단어를 던질 때는 반드시 구체적인 구현 범위를 직접 쪼개주어야 합니다.
- 추천: 어떤 데이터를 기준(관심 카테고리, 최근 조회 등)으로 추천하는지, 결과가 없을 때의 대체 화면은 무엇인지 명시합니다.
- 결제: 단건/정기결제 여부, 환불/부분취소/쿠폰/포인트 포함 여부를 분리합니다.
- 채팅: 실시간 채팅(Socket)인지 단순 메시지인지, 이미지 첨부 및 읽음 표시가 필요한지 명시합니다.
- 관리자: 단순 조회용인지, 수정/승인/권한 관리 기능까지 포함하는지 구체화합니다.
- 검색: 단순 키워드 매칭인지, 필터/정렬/자동완성/검색어 추천이 포함되는지 정의합니다.
- 위치 기반: GPS 권한 거부 시 대체 흐름, 지도 사용 여부, 거리순 정렬 방식 등을 명시합니다.
🛠️ 기능 명세 (동사 단위 쪼개기)
- [ ] 결제 실패 응답 발생 시, 에러 코드(LIMIT_EXCEEDED)에 따른 전용 안내 메시지를 화면에 노출한다.
- [ ] "다른 카드로 결제" 버튼 클릭 시, 등록된 카드 정보 목록을 가져오는 GET /api/v1/cards API를 호출하고 모달 화면을 띄운다.
- [ ] 사용자가 모달에서 새로운 카드를 선택하고 재시도 버튼을 누르면, 새로운 카드 토큰을 바인딩하여 결제 요청 POST /api/v1/payments/retry API를 호출한다.
4. 이번 버전에 만들지 않을 것 (Out of Scope)
의도적으로 구현 범위에서 제외할 항목을 명확히 선언하여 AI가 무분별하게 코드를 팽창시키는 현상(Scope Creep)을 방지합니다.
- 1차 MVP 제외 범위:
- [x] 신규 카드 추가 등록 기능 (기존 등록된 카드 중에서만 선택 가능)
- [x] 신용카드 외 간편결제(네이버페이, 카카오페이) 재시도 경로
- [x] 할부 개월 수 변경 기능
5. 기술 사양 및 AI 데이터 스키마 (Technical Specs)
AI 코딩 도구가 기술적 일관성을 유지할 수 있도록 핵심 데이터 구조와 API 계약을 박아둡니다.
📊 데이터 구조 (JSON Schema 예시)
{
"retry_payment_request": {
"original_transaction_id": "string (UUID)",
"new_card_id": "string (UUID)",
"retry_attempt": "integer (max: 3)"
},
"retry_payment_response": {
"status": "string (SUCCESS | FAILED)",
"transaction_id": "string (UUID)",
"error_message": "string (nullable)"
}
}
- 프론트엔드 스택: React 18, Tailwind CSS
- 백엔드 스택: Node.js (Express), PostgreSQL
- AI 프롬프트용 명시 제약: "재시도 결제 호출 시 절대 데이터베이스 세션을 분리하지 말고 단일 트랜잭션 내에서 처리할 것."
6. 예외 상황 및 에러 핸들링 (Edge Cases)
실제 배포 단계에서 실패율을 획기적으로 낮추기 위해 예상되는 엣지 케이스를 최소 5개 이상 사전 정의합니다.
- 모든 등록 카드가 결제 실패(한도 초과 등)하는 경우: "결제 실패가 반복되었습니다. 다른 결제 수단을 사용하거나 고객센터로 문의해주세요." 메시지와 함께 메인 화면으로 돌아가는 버튼 노출.
- 결제 요청 대기 중 네트워크가 단절되는 경우: 무한 로딩을 방지하기 위해 15초 타임아웃 처리 후 "네트워크 연결이 불안정합니다. 잠시 후 다시 시도해 주세요." 알림 노출.
- 사용자가 동일 재시도 요청을 광클(Double Click)하는 경우: 요청 전송과 동시에 재시도 버튼을 비활성화(Disabled) 처리하여 중복 결제를 원천 차단함.
- 결제 진행 중에 사용자가 브라우저를 강제 종료하는 경우: 웹소켓 혹은 다음 진입 시 세션 복구 로직을 통해 미결제 상태를 확인하고 이전 트랜잭션의 결과를 추적하는 모달 노출.
- 보안 세션(토큰)이 만료된 상태에서 재시도를 호출하는 경우: 세션 만료 에러 감지 시 즉시 로그인 페이지로 리다이렉트하되, 로그인 성공 후 이전 결제 실패 페이지(모달 상태 유지)로 다시 복구함.
7. 성공 지표 및 검수 기준 (Metrics & QA Criteria)
구현이 완료되었을 때 배포 및 통과 여부를 객관적으로 검증할 수 있는 기준을 수립합니다.
🎯 성공 지표 (KPI)
- 핵심 지표: 결제 실패 단계 이탈율 15%p 감소 (목표: 8% $\rightarrow$ 6.8%)
- 보조 지표: 결제 재시도 기능 사용 후 최종 결제 완료율 30% 이상 확보
- 가드레일 지표: 결제 재시도 추가로 인한 평균 결제 처리 응답 시간 증가 폭 +300ms 이내 유지
🧪 인수 기준 (Acceptance Criteria)
- [ ] 사용자는 결제 실패 화면에서 페이지 이탈 없이 등록 카드 모달을 띄울 수 있어야 한다.
- [ ] 다른 카드를 선택하여 결제 재시도 시, 새로운 트랜잭션이 안전하게 실행되고 성공 시 주문 완료 페이지로 바르게 이동해야 한다.
- [ ] 중복 결제를 방지하기 위해 재시도 API가 수행되는 동안 화면의 모든 인터랙션 버튼은 잠금 상태여야 한다.
[TRD] AI 기반 실시간 감정 분석 및 맞춤형 답변 추천 서비스 (v1.0)
본 문서는 Kamen Rider Build 개발 방식과 같이 AI 코딩 에이전트(Cursor, Claude Code 등)가 자의적으로 코드를 추측하여 왜곡하는 것을 방지하고, 단번에 일관된 코드를 구현할 수 있도록 설계된 실무형 기술 요구사항 정의서(TRD) 예제입니다.
1. 기술 스택 및 개발 환경 (Tech Stack)
- Frontend: HTML5, CSS3 (Tailwind CSS CDN 활용), Vanilla JavaScript (ES6+)
- Sentiment Engine: sentiment.js (클라이언트 사이드 로컬 감정 분석 라이브러리)
- Hosting/Deployment: GitHub Pages (정적 웹사이트 배포)
- Target IDE: Cursor / Claude Code (Agentic Mode 대응)
2. 시스템 아키텍처 및 데이터 흐름 (Architecture & Data Flow)
사용자가 텍스트를 입력하면 외부 서버 통신 없이 브라우저 단에서 즉시 처리되는 단일 페이지 애플리케이션(SPA) 구조입니다.
[사용자 UI (텍스트 입력)]
│
▼ (이벤트 트리거: 실시간 입력 및 분석 버튼)
[Core Controller (app.js)]
│
├────────────────────────┐
▼ (로컬 라이브러리 분석) ▼ (결과 기반 메시지 매핑)
[sentiment.js 엔진] [Response Mapper]
│ (Score 반환) │ (답변 템플릿 선택)
└───────────┬────────────┘
▼
[UI Renderer (결과 및 추천 답변 렌더링)]
3. 핵심 자료 구조 및 데이터 스키마 (Data Schema)
AI 코딩 툴이 임의로 데이터 필드명을 짓지 못하도록 명확한 JSON 스키마 규격을 정의합니다.
3.1. 감정 분석 결과 객체 스키마 (EmotionResult Interface)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EmotionResult",
"type": "OBJECT",
"properties": {
"score": {
"type": "NUMBER",
"description": "분석된 문장의 환산 점수 (음수: 부정, 0: 중립, 양수: 긍정)"
},
"comparative": {
"type": "NUMBER",
"description": "단어 수 대비 평균 감정 점수 (score / 단어 개수)"
},
"sentiment": {
"type": "STRING",
"enum": ["POSITIVE", "NEGATIVE", "NEUTRAL"],
"description": "최종 판정된 감정 상태"
},
"tokens": {
"type": "ARRAY",
"items": { "type": "STRING" },
"description": "공백 기준 분석 대상 단어 목록"
}
},
"required": ["score", "comparative", "sentiment", "tokens"]
}
4. API & 주요 핵심 함수 규격 (Core Functions)
4.1. analyzeEmotion(text)
- 설명: 입력받은 텍스트를 형태소 및 단어 단위로 쪼개어 sentiment.js 라이브러리로 점수를 산출하고 감정을 판정합니다.
- 매개변수: text (String) - 사용자 입력 텍스트
- 반환값: EmotionResult (Object) - 위의 3.1 스키마 규격을 따르는 객체
- 구현 로직:
- score > 0 이면 최종 sentiment를 "POSITIVE"로 판단합니다.
- score < 0 이면 최종 sentiment를 "NEGATIVE"로 판단합니다.
- score == 0 이면 최종 sentiment를 "NEUTRAL"로 판단합니다.
4.2. generateRecommendedResponse(sentiment)
- 설명: 판정된 감정 분류에 맞춰 사용자에게 위로, 공감, 축하 등의 맞춤형 추천 메시지를 반환합니다.
- 매개변수: sentiment (String) - "POSITIVE" | "NEGATIVE" | "NEUTRAL"
- 반환 데이터 예시:
- POSITIVE: "당신의 긍정적인 기운이 여기까지 느껴지네요! 이 기분을 일기나 SNS에 기록해보는 건 어떨까요?"
- NEGATIVE: "오늘 조금 힘든 하루를 보내셨군요. 따뜻한 차 한 잔을 마시며 잠시 쉬어가는 것을 추천해 드려요."
- NEUTRAL: "차분하고 평온한 상태이시군요. 오늘 남은 하루도 무탈하고 편안하게 보내시길 바랍니다."
5. 예외 처리 및 에러 핸들링 (Edge Cases & Error Handling)
AI 도구가 반드시 대응해야 하는 4가지 예외 상황의 처리 기준입니다.
발생 상황 (Edge Case)대처 규칙 및 노출할 사용자 메시지시스템 액션
| 빈 값 제출 (텍스트 미입력 후 분석 시도) | "텍스트를 입력해주세요. 공백은 분석할 수 없습니다." | 빨간색 경고 텍스트 노출 및 함수 실행 중단 |
| 길이 제한 초과 (1000자 초과 입력) | "최대 1,000자까지 입력 가능합니다. 현재 입력 수: {N}자" | 입력을 1000자에서 잘라내거나(Truncate) 분석을 제한 |
| 분석 실패/라이브러리 로드 에러 | "감정 분석 엔진에 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." | 콘솔에 Error: Sentiment library not loaded 로깅 및 빈 UI 복구 |
| 특수문자/이모지만 입력 | "분석할 수 있는 단어가 부족합니다. 텍스트를 포함해 입력해 주세요." | score는 0으로 처리하되, sentiment는 "NEUTRAL" 처리 |
6. 품질 검수 기준 및 테스트 케이스 (QA & Test Cases)
AI가 스스로 코드를 짠 후 아래의 테스트 조건을 실행해 통과하는지 검증하는 구체적 케이스입니다.
테스트 케이스 1: 긍정 감정 테스트
- 입력 텍스트: "오늘 기분이 정말 최고야! 행복한 일만 가득해."
- 기대 score: > 0 (양수)
- 기대 sentiment: "POSITIVE"
테스트 케이스 2: 부정 감정 테스트
- 입력 텍스트: "너무 짜증나고 화나며 우울한 하루였어."
- 기대 score: < 0 (음수)
- 기대 sentiment: "NEGATIVE"
테스트 케이스 3: 중립 감정 테스트
- 입력 텍스트: "책상 위에 연필과 노트가 무작위로 놓여 있다."
- 기대 score: 0
- 기대 sentiment: "NEUTRAL"
7. 명시적 제약 조건 (Strict Constraints - AI 통제 전용)
- 외부 통신 일체 금지: 모든 비즈니스 로직과 분석은 클라이언트의 브라우저 내부에서만 완결되어야 합니다. 데이터 보호를 위해 외부 API(예: Google, OpenAI)를 비동기 호출하는 코드는 일체 작성하지 마십시오.
- 의존성 보존: sentiment.js는 <script> 태그를 통한 CDN 형태로 주입해야 하며, 별도의 npm install 패키지 설치나 webpack 번들링 과정을 강제하는 아키텍처 설계를 배제하십시오.
- UI/UX 심플리티: Tailwind CSS를 적용하되 모바일 가독성을 고려한 반응형(Responsive) 카드 레이아웃으로 설계하고, 복잡한 프레임워크(React, Vue 등)는 사용하지 않는 순수 정적 HTML 형태를 유지하십시오.
