이 장이 끝나면: 온티켓 저장소 안에 web/(화면)과 api/(서버) 두 개의 앱이 생겨 있고, 내 컴퓨터에서 두 개의 개발 서버(3000번·8787번)가 동시에 돌며 서로 통신한다. 그리고 이 큰 작업을 Plan Mode — 실행 전에 계획부터 받아 승인하는 방식 — 로 진행하는 법을 익힌다.
기능은 아직 없지만 구조는 완성된 골조를 뼈대라고 한다. 폴더 구조가 잡혀 있고, 개발 서버가 뜨고, 빈 화면이 나오는 상태. 건축으로 치면 방은 비어 있지만 기둥과 배관이 들어간 골조 완공이다.
'뼈대'를 개발 현장에서는 보일러플레이트(boilerplate) 라고 부른다. 이 말의 유래를 알아 두면 개념이 또렷해진다. 옛날 신문사에는 여러 지방 신문이 똑같이 받아 싣던 공용 기사 판이 있었는데, 보일러(증기 기관)를 만들 때 쓰던 두꺼운 금속판처럼 생겨서 '보일러플레이트'라 불렀다. 즉 매번 똑같이 반복되는, 미리 찍어 둔 공통 판이라는 뜻이다. 어떤 웹 앱을 만들든 거의 똑같이 필요한 것들 — 폴더 배치, 설정 파일, "화면 하나가 뜨는 최소 상태" — 을 미리 세워 둔 공통 판, 그게 뼈대다.
여기서 초심자가 자주 하는 오해 하나를 풀어 두자. 뼈대는 '대충 만든 미완성품'이 아니다. 오히려 그 반대다. 기능은 0이지만 구조는 100인 상태가 뼈대다. 벽지도 가구도 없지만, 기둥이 어긋나 있거나 배관이 빠져 있으면 골조 완공이라 부르지 않는 것과 같다. 우리가 이 장에서 만드는 것도 화면에 보이는 것은 거의 없지만, 그 뒤에서 두 앱이 제대로 자리 잡고 서로 통신까지 하는, 구조적으로는 완성된 상태다.
왜 기능부터 만들지 않고 뼈대부터 세우나. 이후의 모든 기능이 이 구조 위에 쌓이기 때문이다. 구조 없이 기능부터 만들면 기능마다 제멋대로의 위치와 방식이 생기고, 사흘만 지나도 "그 코드가 어디 있더라"가 시작된다. 반대로 뼈대가 서면 AI에게 시키는 일이 단순해진다 — "이벤트 목록 기능을 구조에 맞게 추가해줘."
이 문장의 "구조에 맞게" 가 뼈대를 먼저 만드는 실질적인 이점이다. 뼈대가 없으면 AI에게 기능을 시킬 때마다 "이건 어느 폴더에? 화면 코드와 서버 코드를 어떻게 나눠? 설정은 어디에?"를 매번 다시 정해야 한다. 뼈대가 있으면 그 판단이 이미 구조에 박혀 있어서, AI도 사람도 "정해진 자리에 넣기"만 하면 된다. 결정을 한 번 해 두고 계속 재사용하는 것 — 이것이 뼈대를 먼저 세우는 근본 이유다.
그리고 뼈대는 반드시 돌아가는 상태로 만든다. 빈 화면이라도 브라우저에 떠야 한다. 동작하는 최소한의 상태를 확보하고 시작하면, 이후 뭔가 깨졌을 때 "마지막으로 돌아가던 지점"이 존재하게 된다.
이 원칙에는 이름이 있다. 개발자들은 이를 "돌아가는 뼈대(walking skeleton)" 라고 부른다. 걷지는 않지만 뼈만 있어도 일단 서 있고 움직이는 최소한의 골격이라는 뜻이다. 왜 중요한가. 처음부터 기능을 잔뜩 만들어 놓고 한꺼번에 실행하면, 안 될 때 어디서부터 안 되는지를 알 수 없다. 화면 문제인지, 서버 문제인지, 둘을 잇는 연결 문제인지, 애초에 설치가 잘못된 건지 — 후보가 너무 많다. 반대로 "빈 화면이 뜬다"는 최소 상태를 먼저 확보하면, 그 뒤로는 한 번에 한 조각씩 올리면서 "방금 올린 것 때문에 깨졌다"를 바로 알 수 있다. 되돌아갈 안전지대를 먼저 만들고 전진하는 것이다.
[짚고 가기] "화면에 아무것도 없는데 이게 왜 성과냐"는 생각이 들기 쉽다. 여기서는 집짓기에 빗대면 잘 와닿는다. 지금은 집을 지을 땅을 고르고 기둥을 세운 것이다. 벽지는 내일 발라도 되지만, 기둥이 어긋나면 다 뜯어야 한다. 뼈대 단계의 결과물이 눈에 화려하지 않은 건 당연하다. 대신 "이 위에 이틀간 모든 기능이 얹힌다"는 큰 그림을 기억하면, 지금 무엇을 하고 있는지가 분명해진다.
아키텍처 결정(분리형)에 따라 저장소 하나에 앱 두 개를 만든다.
onticket/
├── CLAUDE.md ← 프로젝트 규칙 (계속 자란다)
├── docs/ ← PRD, 화면기획서, ADR (ERD·API 명세는 뒤에서 채워진다)
├── design/ ← 토큰, 금지목록, 시안
├── web/ ← 프론트엔드 앱 (Next.js) → 화면 전담
└── api/ ← API 서버 앱 (Hono) → 판정 전담
왜 앱을 하나가 아니라 둘로 나누나. 온티켓에는 성격이 전혀 다른 두 가지 일이 있다. 하나는 사람에게 보여주는 일(이벤트 목록 화면, 예매 버튼, 티켓 QR을 그리는 화면)이고, 다른 하나는 판정하고 지키는 일(이 좌석이 정말 남았는가, 결제가 진짜 됐는가, 이 주문번호가 유효한가)이다. 앞의 일은 web/(프론트엔드), 뒤의 일은 api/(백엔드) 가 맡는다. 표로 정리하면 이렇다.
| 앱 | 별명 | 맡는 일 | 어디서 도나 |
|---|---|---|---|
| web/ | 화면 전담 | 사용자가 보고 클릭하는 모든 것을 그린다 | 나중에 Vercel에 배포 |
| api/ | 판정 전담 | 잔여석·결제·주문의 실제 상태를 확인하고 판정한다 | 나중에 Cloudflare Workers에 배포 |
이 둘을 굳이 나누는 이유는 뒤 장들에서 실습하며 배우게 되지만, 지금 한 줄로만 짚어 두자 — 화면은 사용자의 브라우저에서 돌고, 판정은 서버에서 돌아야 안전하기 때문이다. "이 좌석 남았음"을 브라우저에서 마음대로 판정하게 두면 누구나 조작할 수 있다. 그래서 돈과 재고가 걸린 판정은 사용자 손이 닿지 않는 서버(api/)로 옮긴다. web/과 api/가 폴더부터 갈라져 있는 것은 이 역할 분담을 코드 구조에 새겨 두는 것이다.
이렇게 저장소 하나에 여러 앱을 담는 방식을 모노레포(monorepo) 라고 한다. 구글·토스 같은 회사들이 수백 개 프로젝트를 한 저장소에 담는 것도 같은 개념이다. 우리 규모에서의 이점은 단순하다 — 관련된 모든 것(문서·토큰·두 앱)이 한 폴더에 있으니 AI가 전체 맥락을 한 번에 본다.
'모노레포'라는 말을 풀면 mono(하나) + repo(repository, 저장소) 다. 저장소는 코드가 사는 하나의 창고를 말한다. 반대 개념은 멀티레포(multi-repo) — 앱마다 창고를 따로 두는 방식이다. 둘을 나란히 두면 이렇다.
| 방식 | 뜻 | 장점 | 단점 |
|---|---|---|---|
| 모노레포 | 창고 하나에 web/·api/·문서를 다 넣는다 | 전체를 한눈에 본다. AI가 두 앱을 오가며 맥락을 잃지 않는다. 함께 고쳐야 할 변경을 한 번에 한다 | 아주 커지면 창고가 무거워진다 |
| 멀티레포 | web 창고, api 창고를 따로 둔다 | 각 팀이 독립적으로 움직인다. 대규모 조직에 맞다 | 두 창고를 오가야 해서 전체 맥락을 잡기 어렵다 |
우리가 모노레포를 고르는 이유는 규모가 작고, 무엇보다 AI에게 맡기기 때문이다. AI는 폴더 하나를 통째로 읽을 때 가장 똑똑하다. web에서 부르는 주소와 api가 응답하는 주소가 같은 창고 안에 있으면, "이쪽 화면이 저쪽 서버의 이 응답을 쓴다"는 연결을 AI가 스스로 파악한다. 창고가 둘로 갈라져 있으면 그 연결이 AI의 시야 밖으로 나간다. 온티켓처럼 web과 api가 긴밀하게 붙어 통신하는 서비스에서는, 둘을 한 창고에 두는 편이 사람에게도 AI에게도 압도적으로 편하다.
[짚고 가기] 모노레포를 "폴더를 나눈 것"으로만 이해하면 절반만 안 것이다. 핵심은 "따로 배포되는 독립된 앱들을 한 창고에 함께 둔다"이다. web은 Vercel로, api는 Cloudflare Workers로 서로 다른 곳에 배포되지만, 지금은 한 폴더 안에서 함께 자란다. "한 집에 살지만 각자 직장이 다른 형제"라고 생각하면 감이 잡힌다. 이 구분을 잡아 두면 뒤 장의 배포에서 "왜 하나씩 따로 올리지?"라는 혼란이 없다.
이 장에서 처음으로, 혹은 새로운 얼굴로 등장하는 도구들이 있다. Claude Code를 설치할 때 "Node.js가 필요 없다"고 했던 바로 그 Node.js가 여기서 반대로 필요해진다. 헷갈릴 수 있는 대목이니 하나씩 정확히 잡고 간다.
Node.js는 브라우저 밖에서 JavaScript를 실행하는 프로그램이다. Claude Code 설치 때는 필요 없다고 했는데, 지금 필요해진 이유가 있다 — 우리가 만들 앱들의 개발 도구가 Node 위에서 돌기 때문이다. Next.js의 개발 서버도, Hono 프로젝트의 로컬 실행 도구도 Node로 만들어져 있다. Claude Code(우리의 AI 도구)와 Node(우리 앱의 재료)는 별개다 — 혼동하지 말자.
여기서 설치 때의 이야기를 다시 짚으며 확실히 구분해 두자. 그때 "Node.js는 설치하지 않아도 된다"고 한 것은 Claude Code라는 도구 자체를 설치할 때의 이야기였다. Claude Code는 네이티브 설치(운영체제용 실행 파일 직접 설치)로 깔리므로 Node가 필요 없다.
그런데 이 장부터는 상황이 다르다. 우리는 이제 우리 소유의 앱(web/·api/) 을 만든다. 그리고 그 앱들이 쓰는 개발 도구 — 화면을 즉시 갱신해 주는 Next.js 개발 서버, api를 내 컴퓨터에서 돌려 보는 로컬 실행 도구 — 가 전부 Node 위에서 돌아간다. 비유하면 이렇다.
목수는 Node 없이도 오지만, 목수가 지어 줄 우리 집(web/·api/)이 실제로 불이 들어오고 물이 나오려면 Node라는 기반 설비가 그 땅에 깔려 있어야 한다. 그래서 Claude Code를 설치할 때는 안 깔았고, 지금은 깐다. 이 둘은 서로 무관한 별개의 이유다.
'Node.js'라는 이름도 잠깐 풀어 두자. 원래 JavaScript는 웹 브라우저 안에서만 돌던 언어였다(버튼을 누르면 반응하는 화면 동작 같은 것). 2009년에 이 JavaScript를 브라우저 밖으로 꺼내 서버·PC에서도 돌게 만든 것이 Node.js다. 'node'는 네트워크의 한 점(노드)을 뜻하는데, 여러 노드가 연결된 서버 프로그램을 JavaScript로 만들자는 발상에서 나온 이름이다. 덕분에 오늘날 수많은 개발 도구가 Node 위에서 돌아가고, 우리가 쓸 Next.js와 Hono의 개발 도구도 그중 하나다.
[짚고 가기] "처음 설치할 땐 Node 필요 없다더니 왜 지금 깔라고 하지?"라는 의문이 들 수 있다. 이 의문이 든다면 오히려 좋은 신호다 — 설치 때의 설명을 제대로 이해했다는 뜻이다. 정리하면 이렇다. 그때는 Claude Code(도구)를 깔았고, 지금은 우리 앱(제품)을 만든다. Claude Code는 Node가 필요 없지만, 우리가 만들 앱은 Node 위에서 돈다. 도구와 제품은 별개다. 이 구분을 확실히 잡아 두면 이후 설치 단계가 매끄럽다.
패키지는 남이 만들어 공개해 둔 코드 부품이다. 날짜 계산, QR 생성, 결제 연동 — 세상 대부분의 기능은 이미 누군가 패키지로 만들어 두었고, 현대 개발은 이 부품들을 조립하는 일에 가깝다.
이 발상은 아주 중요하다. 초심자는 흔히 "개발 = 모든 것을 밑바닥부터 짜는 일"로 상상하지만, 실제 현대 개발은 레고 조립에 훨씬 가깝다. QR 코드를 그리는 법, 날짜를 다루는 법, 결제사와 통신하는 법 같은 것은 이미 수많은 사람이 다듬어 공개해 둔 부품(패키지)이 있다. 우리는 그 부품을 가져다 우리 서비스에 맞게 끼우기만 하면 된다. 좋은 개발이란 "무엇을 직접 짜고 무엇을 부품으로 가져올지"를 잘 판단하는 일이고, 이 판단조차 상당 부분 AI에게 맡길 수 있다.
'패키지(package)'는 말 그대로 '꾸러미'다. 코드 몇 개를 하나로 묶어 이름표(패키지 이름)와 버전을 붙여 배송 가능하게 만든 꾸러미라는 뜻이다. 이 꾸러미들이 모여 사는 거대한 창고를 레지스트리(registry) 라고 하고, JavaScript 세계의 대표 창고가 npm 레지스트리다. 전 세계 개발자가 만든 수백만 개의 부품이 여기 쌓여 있고, 우리는 명령 한 줄로 원하는 부품을 이 창고에서 내려받는다.
패키지 매니저는 그 부품을 내려받고 버전을 관리하는 도구다. Node를 설치하면
기본으로 딸려 오는 것이 npm이고, 우리는 더 빠르고 디스크를 아끼는 pnpm을
쓴다. 프로젝트가 어떤 패키지를 쓰는지는 각 앱의 package.json 파일에 기록된다 —
이 파일이 있으면 누구 컴퓨터에서든 같은 부품 구성을 재현할 수 있다.
여기 나온 세 가지 이름 — npm, pnpm, package.json — 을 표로 정리한다. 셋은 자주 같이 등장하므로 관계를 잡아 두면 편하다.
| 이름 | 무엇 | 우리와의 관계 |
|---|---|---|
| npm | Node에 기본으로 딸려 오는 패키지 매니저. 이름은 'Node Package Manager' | 우리는 pnpm 자체를 설치할 때 한 번만 쓴다 |
| pnpm | npm을 개선한 패키지 매니저. 더 빠르고 디스크를 훨씬 아낀다 | 이 과정에서 우리 앱의 부품 관리를 맡는 도구 |
| package.json | 이 앱이 어떤 부품을 어떤 버전으로 쓰는지 적어 둔 명세서 | 앱마다 하나씩 있다. AI가 관리한다 |
package.json은 앱의 부품 명세서(자재 목록) 라고 이해하면 된다. 요리로 치면 레시피의 재료 칸이다. "밀가루 200g, 설탕 50g…"처럼 "이 부품을 이 버전으로 쓴다"가 줄줄이 적혀 있다. 이 명세서가 있으면, 다른 사람이(혹은 나중의 내가, 혹은 배포될 서버가) 같은 재료를 정확히 같은 버전으로 한 번에 갖출 수 있다. 그래서 부품 파일 자체를 일일이 주고받지 않아도, 이 명세서 하나와 명령 한 줄이면 어느 컴퓨터에서든 동일한 구성이 재현된다. 이 재현성이 협업과 배포의 바탕이다.
그럼 왜 npm을 두고 pnpm을 쓰나. 이름의 p는 'performant(성능 좋은)'에서 왔다. 가장 큰 차이는 디스크를 아끼는 방식이다. npm은 프로젝트마다 같은 부품을 매번 새로 복사해 쌓지만, pnpm은 컴퓨터 한 곳에 부품을 딱 한 벌만 저장해 두고 각 프로젝트가 그것을 '가리키게'(링크) 한다. web과 api 두 앱이 같은 부품을 쓴다면 두 벌이 아니라 한 벌만 있으면 된다는 뜻이다. 그래서 설치가 빠르고 디스크도 절약된다 — 우리처럼 한 창고에 여러 앱을 두는 모노레포에서 특히 잘 맞는다.
[짚고 가기] "패키지가 왜 필요하지, 직접 짜면 안 되나?" 싶다면 QR 코드를 떠올려 보자. QR 코드 하나 그리려면 그 규격을 밑바닥부터 구현해야 하는데, 그건 이미 수천 명이 검증한 부품이 있다. 그걸 직접 짜는 건 바퀴를 다시 발명하는 일이다. pnpm과 npm의 세부 차이는 지금 깊이 알 필요 없다. "npm보다 빠르고 디스크 아끼는 요즘 도구" 정도로 이해하고, 명령만 정확히 치는 데 집중하면 된다.
개발 서버(dev server) 는 내 컴퓨터 안에서만 도는 미리보기용 서버다. 코드를 고치면 화면이 즉시 갱신된다(배포와 무관하다 — 아직 인터넷에 없다).
'서버(server)'라는 말부터 풀자. 서버는 원래 '무언가를 제공(serve)하는 쪽'을 가리킨다. 식당에서 음식을 내오는 사람을 서버라 부르듯, 컴퓨터 세계의 서버는 요청을 받으면 무언가(웹 페이지, 데이터)를 내주는 프로그램이다. 요청하는 쪽은 클라이언트(client) — 우리의 브라우저가 클라이언트다. 브라우저가 "이 페이지 줘"라고 요청하면 서버가 그 페이지를 내준다. 이 요청-응답이 웹의 기본 동작이다.
그럼 왜 '개발' 서버인가. 진짜 서비스를 인터넷에 올리기 전에, 내 컴퓨터 안에서만 돌려 보며 개발하기 위한 서버이기 때문이다. 개발 서버의 가장 큰 매력은 핫 리로드(hot reload) 다 — 코드를 저장하는 순간 브라우저 화면이 자동으로 바뀐다. 매번 껐다 켜거나 새로고침할 필요 없이, 고치는 즉시 결과가 보인다. 그래서 개발 서버를 한 번 띄워 두고 계속 켜 둔 채로 작업한다. 뒤에서 "서버는 켜 두는 프로그램"이라 터미널을 점유한다고 하는 것도 이 때문이다.
localhost는 "내 컴퓨터 자신"을 가리키는 주소이고, 포트는 한 컴퓨터 안에서 여러 서버를 구분하는 번호다. 우리는 두 서버를 띄우므로 포트도 두 개다.
이 두 단어를 조금 더 풀어 두자. localhost는 직역하면 '이 지역(local)의 host(주인
컴퓨터)' — 즉 지금 내 앞의 이 컴퓨터 자신을 뜻하는 특별한 주소다. 개발 서버는
인터넷에 올라간 게 아니라 내 컴퓨터 안에서만 돌기 때문에, 그 서버에 접속하려면
바깥 주소가 아니라 "나 자신"을 가리키는 localhost로 접속한다. 옆자리 사람이
http://localhost:3000을 열면 내 온티켓이 아니라 그 사람 자신의 컴퓨터를 찾는다는
뜻이다(그래서 아직 아무것도 안 뜬다). localhost는 오직 내 컴퓨터에서만 통하는 주소다.
포트(port) 는 '항구'라는 뜻이다. 한 컴퓨터라는 큰 항구에 여러 배(서버)가 동시에
드나들 수 있는데, 어느 배가 어느 부두에 대는지를 정하는 부두 번호가 포트다. 컴퓨터
하나 안에서 web 서버와 api 서버가 동시에 도는데, 둘 다 그냥 localhost라고만 하면
브라우저는 어느 서버로 가야 할지 모른다. 그래서 부두 번호(포트)로 구분한다 —
3000번 부두로 가면 web, 8787번 부두로 가면 api. 주소 뒤의 :3000, :8787이
바로 그 부두 번호다.
| 주소 | 무엇이 뜨나 |
|---|---|
| http://localhost:3000 | web (Next.js 개발 서버의 기본 포트) |
| http://localhost:8787 | api (Hono/Workers 로컬 실행 도구의 기본 포트) |
이 3000·8787이라는 숫자는 우리가 정한 게 아니라 각 도구의 관례적 기본값이다. Next.js는 전통적으로 3000번을, Cloudflare Workers의 로컬 실행 도구는 8787번을 기본으로 쓴다(8787은 알파벳 'W-R-A-N-G-L-E-R' 도구에서 유래한 관례다). 숫자 자체에 깊은 의미는 없고, "이 앱은 이 부두를 쓴다"는 약속일 뿐이다. 필요하면 바꿀 수도 있지만, 기본값을 그대로 쓰면 자료·도구가 다 그 숫자를 기준으로 맞춰져 있어 편하다.
[짚고 가기] localhost와 포트는 처음에 가장 자주 걸려 넘어지는 지점이다. "옆 사람도 localhost:3000 치면 내 걸 볼 수 있나?" 하고 헷갈리기 쉬운데, 답은 아니다. localhost는 각자 자기 컴퓨터를 가리키니 각자 자기 것만 본다. 그럼 남이 보게 하려면? 그게 바로 뒤 장의 배포 이야기로 이어진다. 포트는 "한 컴퓨터의 여러 문(부두)에 번호를 붙인 것"이라고 생각하면 이해가 쉽다.
지금까지의 방식은 지시하면 AI가 곧바로 일하고, 변경 건별로 퍼미션을 받는 것이었다. 작은 일에는 충분하다. 그런데 뼈대 생성처럼 수십 개 파일이 생기는 큰 작업이라면? 건별 승인 수십 번을 기계적으로 누르게 되고, 전체 그림은 아무도 검토하지 않은 채 공사가 끝난다.
이 문제를 조금 더 뜯어 보자. 건별 퍼미션은 훌륭한 안전장치지만 약점이 하나 있다 — 나무는 보여주지만 숲은 안 보여준다. 파일 하나하나가 뜰 때마다 "이 파일 만들까요?"를 묻는데, 스무 번쯤 반복되면 사람은 내용을 읽지 않고 엔터만 연타하게 된다(퍼미션을 처음 만났을 때 경계했던 바로 그 습관이다). 게다가 각 파일이 개별로는 멀쩡해 보여도, 스무 개가 모여 만드는 전체 구조가 내 의도와 맞는지는 건별 창에서 판단할 수 없다. 첫 번째 파일을 승인하는 시점에 나는 아직 스무 번째 파일에 무엇이 올지 모른다.
Plan Mode는 이를 뒤집는다. 이 모드에서 AI는 파일을 만들거나 고치지 못하고 (읽기만 가능), 대신 무엇을 어떤 순서로 할지 계획을 먼저 제시한다. 사람이 계획을 읽고 승인하면 그때 실행이 시작된다. 건별 퍼미션이 '변경 하나'의 결재라면, Plan Mode는 '공사 전체 설계도'의 결재다.
이 대비를 표로 정리하면 성격이 또렷해진다.
| 건별 퍼미션 | Plan Mode | |
|---|---|---|
| 결재 대상 | 변경 하나(파일 한 개) | 작업 전체(설계도) |
| 검토 시점 | 실행 중, 조각마다 | 실행 전, 한 번에 |
| 보이는 것 | 나무(개별 파일) | 숲(전체 계획) |
| 어울리는 일 | 작고 국소적인 변경 | 크고 전체적인 작업 |
핵심은 "읽기는 되지만 쓰기는 안 된다" 는 점이다. Plan Mode의 AI는 우리 폴더의 파일을 마음껏 읽고 조사할 수 있지만, 한 글자도 바꾸지 못한다. 그래서 안전하게 "먼저 파악하고 계획만 세워 봐"를 시킬 수 있다. 계획을 보고 마음에 안 들면 실행이 시작되기 전에 고치거나 아예 무를 수 있다. 아직 아무것도 바뀌지 않았기 때문이다. 이것이 큰 작업에서 Plan Mode가 주는 안심이다.
입력창에서 Shift+Tab 을 누르면 모드가 순환한다. 화면 하단 표시가
plan mode 로 바뀌면 켜진 것이다. 계획을 승인하면 실행 단계로 넘어간다.
조금 더 자세히 짚자. Shift+Tab은 모드를 하나씩 돌려 가며 바꾸는 단축키다. 한 번
누를 때마다 다음 모드로 넘어가고, 계속 누르면 다시 처음으로 돌아온다(순환). 우리가
쓸 것은 그중 plan mode이고, 화면 맨 아래에 지금 어떤 모드인지 항상 표시되니 그것을
보고 확인하면 된다. plan mode 표시가 떠 있는 동안 내리는 지시는 "실행하지 말고 계획만
세워라"로 해석된다. 계획을 받고 승인하면 자동으로 실행 모드로 넘어가 공사가 시작된다.
정리하면 흐름은 이렇다.
1. 입력창에서 Shift+Tab을 눌러 하단 표시가 `plan mode`가 되게 한다
2. 큰 작업을 지시한다 (예: "web/과 api/ 두 앱의 뼈대를 만든다…")
3. AI가 파일을 바꾸는 대신, 무엇을 어떤 순서로 할지 계획을 글로 제시한다
4. 사람이 계획을 검토한다 (범위·스택·구조)
5. 승인하면 실행 시작 / 이상하면 계획을 고치라고 지시 (아직 아무것도 안 바뀜)
[짚고 가기] Shift+Tab이 '순환'이라는 점을 놓치면 헷갈리기 쉽다. 한 번 눌렀는데 원하는 모드가 아니면 될 때까지 더 누르면 된다. 화면 하단 표시를 직접 보면서 "지금 뭐라고 떠 있나"를 확인하는 게 가장 확실하다. plan mode에서 지시했는데 AI가 계획 대신 곧바로 파일을 만들려 한다면, 그건 plan mode가 안 켜진 것이다 — 하단 표시를 다시 확인하면 된다.
이 사례들을 조금 더 구체적인 장면으로 풀어 두면, 나중에 실무에서 "이건 Plan Mode 감이다"를 스스로 알아챌 수 있다.
반대로 Plan Mode가 굳이 필요 없는 일도 분명히 해 두자. "이 버튼 색만 바꿔줘", "이 오타 하나 고쳐줘", "이 문구를 이렇게 바꿔줘" 같은 작고 국소적인 변경은 건별 퍼미션으로 충분하다. 오히려 이런 일까지 Plan Mode로 하면 계획을 읽는 시간이 더 든다. Plan Mode는 공짜가 아니라 '계획을 읽는 품'을 요구하므로, 그 품을 들일 값어치가 있는 큰 일에만 쓴다.
공통 기준: 틀렸을 때 비용이 큰 작업, 전체 그림이 필요한 작업이면 Plan Mode.
이 한 문장이 판단의 나침반이다. 스스로에게 두 가지를 물으면 된다. 첫째, "이게 잘못되면 되돌리기 얼마나 어렵나?" — 되돌리기 어려울수록(데이터 삭제, 설정 변경, 대규모 이동) Plan Mode. 둘째, "조각만 봐서는 판단이 안 되고 전체를 봐야 하나?" — 전체 구조가 걸린 일일수록 Plan Mode. 둘 중 하나라도 '그렇다'면 Shift+Tab을 먼저 누른다.
[짚고 가기] Plan Mode를 "항상 켜 두면 안전한 것 아닌가"라고 오해하기 쉬운데, 그렇지 않다. 작은 일까지 계획을 받으면 오히려 느려지고, 계획을 읽지 않고 승인하는 습관이 다시 생긴다. "큰일엔 Plan, 작은 일엔 건별"이라는 감각을 기르는 게 핵심이다. 뼈대 세우기가 '큰일'의 대표 사례이니, 여기서 왜 Plan Mode를 쓰는지 직접 느껴 보면 기준이 자연히 잡힌다.
이제 우리 앱들이 돌아갈 바탕을 깐다. 앞서 말했듯 여기서 까는 Node.js는 Claude Code와 무관한, 우리 앱을 위한 기반 설비다. 순서는 Node를 먼저 깔고(그 안에 npm이 딸려 온다), 그 npm으로 pnpm을 까는 것이다.
$ node --version # v22.x.x 같은 버전이 나오면 성공
$ npm --version
여기서 두 가지를 풀어 두자. 먼저 LTS는 'Long Term Support(장기 지원)'의 약자다. Node는 최신 실험 버전(Current)과 안정 버전(LTS)을 나눠 배포하는데, LTS는 오랫동안 고쳐 주고 안정성이 검증된 판이다. 개발을 처음 배우는 단계에서는 최신 기능보다 안정성이 중요하니 항상 LTS를 고른다. 웹사이트에 보통 두 개의 큰 버튼이 있는데, 그중 'LTS'라고 적힌 쪽을 누르면 된다.
다음으로, 3번의 "터미널을 새로 열고" 를 빠뜨리지 말자. Claude Code를 설치할 때 배운 원리가 여기서도
그대로 적용된다 — 터미널은 열릴 때 사용 가능한 명령 목록을 읽어 들이는데, 방금 Node를
설치했다면 지금 열려 있는 터미널은 새로 생긴 node·npm 명령을 아직 모른다. 그래서
설치 뒤에는 반드시 터미널을 닫고 새로 열어야 확인 명령이 통한다. 이 "새 명령을
인식시키려면 터미널을 다시 연다"는 습관은 앞으로도 계속 쓰인다.
node --version과 npm --version이 각각 버전 숫자를 뱉으면 성공이다. --version은
Claude Code 설치를 확인할 때 봤듯 "너 버전 몇이야?"를 묻는 공통 옵션이고, 버전을 대답한다는 것 자체가
그 프로그램이 제대로 설치돼 실행 가능하다는 증거다. npm은 우리가 따로 깔지 않았는데도
버전이 나오는데, 앞서 말했듯 Node를 깔면 npm이 함께 딸려 오기 때문이다.
$ npm install -g pnpm
$ pnpm --version
-g는 이 컴퓨터 전체에서 쓰게 설치한다는 뜻이다. 이 두 줄이 이 과정에서
직접 치는 마지막 설치 명령에 가깝다 — 이후의 패키지 설치는 전부 AI가 한다.
명령을 한 번 뜯어 보자. npm install은 "부품을 설치해라", pnpm은 설치할 대상의
이름, -g는 옵션이다. 즉 "방금 딸려 온 npm을 써서, pnpm이라는 도구를 이 컴퓨터
전체에 설치해라" 는 뜻이다. 여기서 npm은 pnpm을 까는 데 딱 한 번만 쓰이고, 그
뒤로 우리 앱의 부품 관리는 전부 pnpm이 맡는다.
-g의 g는 'global(전역)' 이다. 옵션 없이 설치하면 지금 폴더의 프로젝트에만
설치되지만, -g를 붙이면 특정 프로젝트가 아니라 컴퓨터 전체에서 쓸 수 있는 도구로
설치된다. pnpm은 어느 프로젝트에서든 부르는 도구이니 전역으로 까는 게 맞다.
pnpm --version이 버전을 뱉으면 준비 완료다. 이제 이 컴퓨터에는 Node(바탕), npm(딸려
온 기본 매니저), pnpm(우리가 쓸 매니저)이 모두 갖춰졌다. 강조하건대, 우리가 직접 치는
설치 명령은 사실상 여기까지다. 앞으로 web/·api/에 필요한 수많은 부품은 전부 AI가
계획하고 설치한다 — 우리는 그 계획을 검토하고 승인할 뿐이다.
[짚고 가기] 설치 단계는 환경마다 걸리는 시간이 다르다. Windows에서
npm install -g가 권한 문제로 막히는 경우가 가끔 있는데, 대개는 터미널을 새로 열거나 Git Bash에서 다시 시도하면 풀린다. 설치가 도는 동안 이 장 실습의 첫 지시를 미리 읽어 두면 대기 시간을 알차게 쓸 수 있다. 그리고 이게 우리가 직접 치는 마지막 설치라는 점을 기억해 두자. 앞으로 설치는 AI에게 맡긴다는 게 이 과정의 방식이다.
큰 작업의 지시일수록 범위의 울타리가 중요하다. 넣을 것·뺄 것·완료 기준.
왜 큰 작업일수록 울타리가 중요한가. 작은 지시는 결과가 조금 빗나가도 금방 알아채고 고치면 그만이다. 그런데 뼈대처럼 수십 개 파일이 한 번에 생기는 작업은, 울타리가 없으면 AI가 "친절하게" 시키지 않은 것까지 만들어 버리기 쉽다 — 로그인 화면, DB 연결, 결제 준비 같은 것을 미리 깔아 두는 식이다. 악의는 없지만, 우리가 아직 원하지 않은 것들이 뼈대에 섞여 들면 나중에 걷어내는 게 일이다. 그래서 "무엇을 넣고, 무엇을 빼고, 무엇이 보이면 끝인지"를 지시 안에 명확한 울타리로 친다. 특히 뺄 것을 명시하는 것이 큰 작업 지시의 핵심 기술이다.
[목표] web/과 api/ 두 앱의 뼈대를 만든다
[제약] 스택 고정: web=Next.js+Tailwind, api=Hono(Cloudflare Workers용), pnpm 사용.
기능은 아직 만들지 않는다 — 인증·결제·DB 연결 전부 제외.
딱 하나, 연결 확인용으로 api에 GET /health 하나만 만들고
web 홈에서 그 응답을 표시한다
[완료] 두 개발 서버가 뜨고, localhost:3000 화면에 api가 보낸 "ok"가 보이면 완료
이 지시문을 한 줄씩 뜯어 보면, 첫 지시를 내릴 때 익힌 "목표 + 완료 기준 + 제약" 의 기본형이 큰 작업 버전으로 확장된 것임을 알 수 있다.
"연결 확인용 /health 하나"에 주목하자. 뼈대 단계에서 기능을 안 만든다면서 왜 이건 만드나 — 두 앱이 실제로 통신하는지를 확인하는 최소 장치이기 때문이다. web 화면에 api의 응답이 보이는 순간, 분리형 구조 전체가 살아 있음이 증명된다.
'/health'라는 이름도 관례에서 왔다. 서버가 살아 있는지 확인하려고 두는 아주 단순한
확인용 주소를 흔히 헬스 체크(health check) 라고 부르고, 그 주소를 관례적으로
/health로 둔다. 병원의 건강검진처럼 "너 살아 있니?"를 묻는 창구다. 물으면
{"status":"ok"} 같은 짧은 답만 돌려주면 된다. 우리는 이 창구 하나를 api에 만들고,
web이 그 창구에 물어 답을 화면에 그리게 함으로써 "화면 앱이 서버 앱과 실제로
말을 주고받는다"를 증명한다. 뼈대에 기능은 없어도 통신의 파이프 하나는 뚫어
두는 것 — 이것이 돌아가는 뼈대의 완성이다.
약한 지시를 강한 지시로. 같은 뼈대라도 어떻게 시키느냐에 따라 결과가 갈린다. 아래 표로 감을 잡아 두자.
| 약한 지시 | 강한 지시 | 무엇을 바꿨나 |
|---|---|---|
| 온티켓 프로젝트 만들어줘 | web/과 api/ 두 앱의 뼈대를 만들어줘. 기능은 아직 없이, 각각 빈 화면·빈 서버가 뜨는 상태까지만 | 목표를 '뼈대'로 좁히고 범위를 못 박음 |
| 프론트랑 백엔드 세팅해줘 | web=Next.js+Tailwind, api=Hono(Workers용), 패키지는 pnpm. 이 스택으로 고정 | 스택을 명시해 AI의 임의 선택을 차단 |
| 필요한 거 다 만들어줘 | 인증·결제·DB 연결은 전부 빼고, 연결 확인용 /health 하나만 | 뺄 것을 명시(가장 중요) |
| 잘 되게 해줘 | 두 서버가 뜨고 localhost:3000에 api의 "ok"가 보이면 완료 | 완료 기준을 눈으로 확인 가능하게 |
맥락을 함께 주면 더 좋다. 여기에 한 줄을 덧붙일 수 있다 — "우리는 이 뼈대 위에 앞으로 이벤트 목록·예매·결제·QR 티켓을 얹을 거야. 지금은 그 토대만." 이렇게 앞으로의 방향을 한 번 알려 주면, AI가 폴더 구조와 설정을 나중에 얹을 기능까지 염두에 두고 잡아 준다. 첫 지시를 내릴 때 익힌 "맥락은 아끼지 말 것"이 큰 작업에서 특히 힘을 발휘한다.
Plan Mode가 내놓은 계획에서 사람이 볼 것은 세 가지다.
이 세 가지를 조금 더 구체적인 '점검 질문'으로 바꿔 두면 계획을 읽을 때 바로 써먹을 수 있다.
| 검토 항목 | 스스로에게 던지는 질문 | 문제 발견 시 |
|---|---|---|
| 범위 초과 | 계획에 로그인·DB·결제·회원가입 같은, 내가 빼라고 한 게 슬쩍 들어와 있진 않나? | "그건 계획에서 빼. 아직이야." |
| 스택 일치 | web은 Next.js 맞나? api는 Hono/Workers 맞나? 엉뚱한 프레임워크·DB·패키지 매니저가 끼었나? | "스택은 web=Next.js, api=Hono로 고정. 다른 건 빼." |
| 구조 | web/과 api/가 제대로 갈라져 있나? 화면 코드가 api/에, 서버 코드가 web/에 섞이진 않았나? | "폴더 구조를 web/·api/ 분리에 맞게 다시." |
특히 첫 번째 범위 초과를 눈여겨보자. AI는 종종 "이왕 만드는 김에" 친절을 베푼다 — 로그인 폼을 미리 깔아 두거나, DB 연결 설정을 넣어 두는 식이다. 나쁜 의도는 없지만 지금 우리가 원한 게 아니다. 계획서에서 이런 '시키지 않은 친절'을 찾아내는 것이 사람의 핵심 검토다.
이상하면 계획 단계에서 고친다 — "DB 설정은 계획에서 빼. 아직이야." (DB를 안 쓰겠다는 게 아니다. DB는 곧이어 DBA 역할이 설계부터 제대로 한다. 뼈대를 세우는 김에 슬쩍 끼워 넣을 일이 아니라는 뜻이다.) 계획 수정은 공짜지만, 실행 후 수정은 공사 재시공이다.
이 "계획 수정은 공짜, 실행 후 수정은 재시공"이라는 원리가 Plan Mode의 존재 이유 전부를 요약한다. 아직 파일이 하나도 안 생겼으니, 계획을 고치는 것은 문장 몇 줄을 주고받는 일에 불과하다. 반면 실행이 끝난 뒤 "아, DB는 빼야 했는데"를 알아채면, 이미 생긴 파일들을 걷어내고 얽힌 설정을 되돌려야 한다. 그래서 틀림을 되도록 '계획 단계'로 앞당겨서 잡는 것 — 이것이 큰 작업을 안전하게 하는 요령이다.
계획을 고치는 지시의 예를 몇 개 더 들어 둔다. 계획이 마음에 안 들 때 이렇게 말하면 된다.
DB 연결이랑 로그인은 계획에서 빼줘. 지금은 뼈대만이야.
api 쪽에 Express가 들어가 있는데, 우리는 Hono로 고정하기로 했어. Hono로 바꿔서 다시 계획해줘.
계획은 좋아. 다만 /health 응답에 시간 같은 건 넣지 말고 {"status":"ok"}처럼 최소로만.
이렇게 계획을 몇 번 주고받아 마음에 들면, 그때 승인해서 실행으로 넘긴다. 주고받는 동안 파일은 하나도 바뀌지 않으니 마음껏 다듬어도 된다.
큰 생성 작업에서는 중간에 에러가 날 수 있다(네트워크, 버전 문제 등). 당황할 것 없다 — 에러 메시지를 그대로 AI에게 보여주는 것이 정석이다. "안 돼"가 아니라 에러 원문을 주면, 대부분 스스로 원인을 찾아 고친다.
이 원리는 첫 지시를 다듬을 때 익힌 "증상만 말하지 말고 근거(에러 원문)를 제공"과 정확히 같다. 사람 눈에는 빨간 에러 글자가 무섭게 보이지만, AI에게 그 글자는 원인을 가리키는 가장 좋은 단서다. "에러 났어"라고만 하면 AI는 짐작으로 헤매지만, 에러 원문을 통째로 붙여 주면 그 안의 파일명·줄 번호·오류 종류를 읽고 원인을 좁힌다. 그래서 막혔을 때의 정석은 늘 같다 — 화면에 뜬 에러를 있는 그대로 복사해서 붙여넣는 것.
에러를 보여줄 때 한 줄 덧붙이면 더 좋다.
(에러 메시지 전체 붙여넣기) 이 에러가 났어. 원인부터 설명하고, 어떻게 고칠지 알려준 다음 고쳐줘.
"원인부터 설명하고"를 붙이면, AI가 무턱대고 코드를 바꾸는 대신 무엇이 왜 문제인지 먼저 말하게 된다. 그 설명을 읽으면 사람도 상황을 이해하게 되고, 엉뚱한 수정을 조기에 걸러 낼 수 있다. 뼈대 생성처럼 처음 겪는 큰 작업에서는 특히, 이렇게 원인을 한 번 짚고 넘어가는 습관이 이후 문제 해결의 근육을 길러 준다.
[짚고 가기] 뼈대 생성은 패키지를 많이 내려받는 작업이라 네트워크 사정에 따라 중간에 멈추거나 에러가 나는 경우가 실제로 잦다. 에러를 보고 당황해 창을 닫아 버리지 말자. 에러는 정상이고, 뜨면 그 글자를 그대로 복사해 붙여넣으면 된다. 일부러 에러를 하나 내 보고 그 글자를 붙여넣어 보면, AI가 스스로 고치는 걸 직접 확인할 수 있다. 그렇게 한 번 겪고 나면 에러가 더는 두렵지 않다.
이제 배운 것을 모두 이어 붙인다. 흐름은 이렇다 — CLAUDE.md의 규칙을 뼈대 단계에 맞게 풀고, Plan Mode를 켜서 뼈대를 계획으로 받고, 검토·승인하고, 두 서버를 띄워 연결을 눈으로 확인한 뒤, 실행 방법을 기록해 둔다.
기획 단계에 걸어 둔 규칙("시키기 전에 코드 파일을 만들지 않는다")을 푼다.
CLAUDE.md를 갱신해줘. "코드 파일을 만들지 않는다" 규칙은 삭제하고,
대신 이렇게: "구조 규칙 — 화면 코드는 web/에만, 판정·데이터 코드는 api/에만.
두 앱의 경계를 넘는 코드를 만들기 전에는 반드시 확인을 구한다."
위 프롬프트를 그대로 복사해 입력하면 된다. 다른 내용으로 바꿔서 해 보고 싶다면 자유롭게 바꿔도 좋다.
왜 이 규칙을 지금 푸나. 기획 단계에서는 "아직 코드를 만들지 마라"는 잠금을 걸어 두었다. 설계가 끝나기 전에 AI가 성급히 코드를 깔아 버리는 걸 막기 위해서였다. 이제 뼈대를 세울 때가 되었으니 그 잠금을 풀어야 한다. 그런데 그냥 푸는 게 아니라, 새로운 규칙으로 갈아 끼운다 — "화면 코드는 web/에만, 판정 코드는 api/에만"이라는 구조 규칙이다.
이 대목이 중요하다. CLAUDE.md는 AI가 매번 읽는 프로젝트의 헌법 같은 파일이다. 여기에 "web과 api의 경계를 지켜라"를 적어 두면, 앞으로 어떤 기능을 시키든 AI가 그 경계를 스스로 지킨다. 뼈대를 세우는 순간에 이 구조 규칙을 못 박아 두는 것은, 이후 이틀간 쌓일 모든 기능이 제자리에 놓이도록 미리 바닥에 선을 긋는 일이다.
Shift+Tab 으로 plan mode로 전환한 뒤, 아래 목표·제약·완료 지시를 입력한다.
순서를 다시 확인하자. 먼저 입력창에서 Shift+Tab을 눌러 화면 하단이 plan mode가
되게 한 다음에 지시를 입력한다. 순서가 바뀌어 지시부터 넣으면 AI가 계획 없이
곧바로 실행하려 들 수 있다. 하단 표시가 plan mode인지 눈으로 확인하고 나서
위 '프롬프트 작성법'에서 다듬은 목표·제약·완료 지시를 붙여넣는다. 그 지시는 다음과 같다.
[목표] web/과 api/ 두 앱의 뼈대를 만든다
[제약] 스택 고정: web=Next.js+Tailwind, api=Hono(Cloudflare Workers용), pnpm 사용.
기능은 아직 만들지 않는다 — 인증·결제·DB 연결 전부 제외.
딱 하나, 연결 확인용으로 api에 GET /health 하나만 만들고
web 홈에서 그 응답을 표시한다
[완료] 두 개발 서버가 뜨고, localhost:3000 화면에 api가 보낸 "ok"가 보이면 완료
위 프롬프트를 그대로 복사해 입력하면 된다. 다른 내용으로 바꿔서 해 보고 싶다면 자유롭게 바꿔도 좋다.
계획이 오면 세 가지(범위·스택·구조)를 검토한다. 특히 시키지 않은 것이 끼어 있는지 본다. 문제없으면 승인 — 실행이 시작된다. 패키지 설치와 파일 생성이 줄줄이 진행되는 것을 지켜보자. 몇 분 걸릴 수 있다.
여기서 '프롬프트 작성법'의 검토 표를 실제로 꺼내 쓴다. 계획서를 처음부터 끝까지 훑으며 세 질문을 던진다 — "내가 빼라고 한 게 들어왔나(범위)? 스택이 Next.js·Hono 맞나(스택)? web/·api/가 제대로 갈라졌나(구조)?" 하나라도 걸리면 거기서 본 수정 지시 예문처럼 "그건 빼줘"로 고치게 하고, 다시 받은 계획을 또 검토한다. 이 주고받음이 낭비처럼 느껴져도, 실행 후에 걷어내는 것보다 비용이 훨씬 적게 든다.
실행이 몇 분 걸리는 이유도 알아 두면 마음이 편하다. 이 단계에서 AI는 web과 api 각각에 필요한 수많은 패키지(부품)를 npm 레지스트리에서 내려받는다. Next.js 하나만 해도 딸린 부품이 상당히 많다. 인터넷에서 부품을 받아 정리하는 시간이라, 진행 막대가 한동안 도는 것은 지극히 정상이다. 조급해하지 말고 지켜보자.
실행이 끝나면 서버를 띄운다. 서버는 켜 두는 프로그램이라 터미널이 하나씩
점유된다 — 그래서 터미널이 두 개 필요하다. VS Code 터미널 우측의 + 를 눌러
새 터미널을 만들 수 있다.
"터미널이 점유된다"는 말을 풀어 두자. 지금까지 우리가 친 명령들(node --version
같은)은 한 번 실행되고 곧바로 끝나서 프롬프트가 다시 돌아왔다. 그런데 개발 서버는
다르다 — 계속 켜 둔 채 요청을 기다리는 프로그램이라, 실행하면 그 터미널이
서버에 붙들려 다른 명령을 받지 못한다(프롬프트가 안 돌아온다). 이건 고장이 아니라
정상이다. 서버가 살아서 대기 중이라는 뜻이다. 그래서 web 서버와 api 서버를 동시에
띄우려면 터미널이 두 개 있어야 한다 — 한 터미널당 서버 하나씩.
새 터미널은 VS Code 터미널 창 우측 위의 + 버튼으로 만든다. 그러면 기존 터미널은
그대로 둔 채 옆에 새 터미널이 하나 더 생긴다. 두 터미널을 나란히 두고, 각각에서
서버 하나씩을 띄운다.
AI가 알려준 명령을 각 터미널에서 실행한다 (대개 이런 모양이다):
# 터미널 1
$ cd web && pnpm dev # → localhost:3000
# 터미널 2
$ cd api && pnpm dev # → localhost:8787
이 명령도 뜯어 보자. cd web은 'change directory' — web 폴더 안으로 들어간다는
뜻이다. &&는 "앞 명령이 성공하면 이어서 뒤 명령을 실행하라"는 연결이다. pnpm dev는
"pnpm아, 이 앱의 개발(dev) 서버를 띄워라"라는 지시다('dev'는 development, 개발용
서버라는 뜻). 즉 한 줄로 "web 폴더로 들어가서 개발 서버를 띄워라" 가 된다. 터미널
2도 같은 구조로 api 폴더의 개발 서버를 띄운다. 두 명령을 각각 다른 터미널에서 실행하면,
두 서버가 서로 다른 부두(3000·8787)에서 동시에 대기하게 된다.
각 터미널에 "ready", "listening" 같은 문구와 함께 주소(localhost:3000 / :8787)가 뜨면 서버가 성공적으로 뜬 것이다. 이제 두 서버가 살아서 요청을 기다린다.
브라우저에서 확인한다.
http://localhost:8787/health → api가 직접 응답하는가 (예: {"status":"ok"})http://localhost:3000 → web 화면에 api가 보낸 값이 표시되는가이 두 단계를 순서대로 확인하는 데는 이유가 있다. 먼저 1번으로 api 혼자서
제대로 답하는지 본다. 브라우저 주소창에 localhost:8787/health를 직접 치면, api
서버에게 직접 "너 살아 있니?"를 묻는 것이다. {"status":"ok"} 같은 답이 화면에 그대로
보이면 api는 정상이다. 그다음 2번으로, web이 그 api에게 물어서 답을 화면에
그리는지 본다. 만약 1번은 되는데 2번이 안 되면, 문제는 api가 아니라 web과 api를
잇는 연결에 있다 — 이렇게 조각을 하나씩 확인하면 어디가 문제인지 좁힐 수 있다.
이 장 첫머리에서 말한 "돌아가는 뼈대"의 이점이 여기서 그대로 드러난다.
2번이 뜨는 순간이 오늘의 이정표다 — 화면 앱이 서버 앱에게 물어서 답을 받아 그렸다. 분리형 구조가 내 컴퓨터에서 살아 움직이고 있다.
이 순간을 조금 음미하자. 지금 화면에는 "ok" 한 단어밖에 없지만, 그 한 단어가 오기까지 web(3000)이 api(8787)에게 요청을 보내고, api가 판정해서 응답을 돌려주고, web이 그 응답을 받아 화면에 그리는 — 온티켓의 모든 기능이 앞으로 따를 바로 그 경로가 한 번 완주된 것이다. 이벤트 목록도, 예매도, 결제도 전부 이 파이프를 타고 흐른다. 뼈대 단계에서 이 파이프 하나를 뚫어 확인해 두었기 때문에, 이제 그 위에 기능을 얹기만 하면 된다.
(참고: 시안 design/home.html은 이제 참고 자료가 된다. 실제 홈 화면은
web/ 안에서 토큰을 이어받아 다시 구현하게 된다.)
[짚고 가기] 이 실습의 클라이맥스는 '연결 확인'의 두 번째 확인이다. "화면에 ok가 떴다"를 확인하는 순간을 놓치지 말자. 이 별것 아닌 ok가 사실은 두 앱이 통신에 성공했다는 증거다. 이 점을 알고 보면, 뼈대 단계의 밋밋함이 성취로 바뀐다. 잘 안 될 때 열에 아홉은 (1) 두 서버 중 하나가 안 떠 있거나, (2) 8787을 직접 열었을 때 이미 안 되는 경우다. 그래서 반드시 1번(api 단독)부터 확인해 문제를 좁혀 나가는 게 좋다.
"이 프로젝트 어떻게 켜더라?"를 미래의 나와 AI가 묻지 않도록 기록한다.
CLAUDE.md에 실행 방법을 추가해줘:
web은 cd web && pnpm dev (3000), api는 cd api && pnpm dev (8787).
둘 다 떠 있어야 정상 동작.
위 프롬프트를 그대로 복사해 입력하면 된다. 다른 내용으로 바꿔서 해 보고 싶다면 자유롭게 바꿔도 좋다.
이걸 왜 기록하나. 오늘은 방금 띄웠으니 명령이 머릿속에 생생하지만, 이틀 뒤의 나는(혹은 내일 이 프로젝트를 처음 여는 동료는) "이거 어떻게 켜더라?"를 반드시 다시 묻게 된다. 그때마다 헤매지 않도록, 실행 방법을 프로젝트의 헌법(CLAUDE.md)에 적어 두는 것이다. 더 중요한 것은 AI도 이 파일을 읽는다는 점이다. CLAUDE.md에 "두 서버를 이렇게 띄운다"가 적혀 있으면, 앞으로 AI에게 "서버 좀 띄워줘"라고만 해도 AI가 이 기록을 보고 정확한 명령을 안다. 기록은 미래의 나와 AI 둘 다를 위한 것이다.
이렇게 뼈대를 세우며 알게 된 것(구조 규칙, 실행 방법)을 그때그때 CLAUDE.md에 쌓아 가는 습관은 이 과정 내내 반복된다. CLAUDE.md는 처음엔 규칙 몇 줄이지만, 프로젝트가 자라며 함께 자라 결국 "이 프로젝트를 아는 모든 것"이 담긴 안내서가 된다.
node --version, pnpm --version 이 버전을 출력한다web/과 api/ 두 앱이 생겼다이것으로 Day 1이 끝난다. 빈 골격이 섰으니, 다음은 이 골격의 api/ 안에 데이터의
설계도(스키마)를 먼저 놓는다. 어떤 데이터를 어떤 모양으로 가질지가 정해져야 그 위에
기능을 올릴 수 있다 — 기능 구현은 그다음이다.