프롬프트에서 프로덕션까지 — Sonnet과 Catalyst를 활용한 풀스택 애플리케이션 구축
요약
Claude와 같은 AI 모델이 기존 아키텍처와 충돌하는 코드를 생성하는 문제를 해결하기 위해, 전체 시스템 컨텍스트를 담은 참조 파일(tracE.md)을 활용하는 전략을 소개합니다. 단순 프롬프팅을 넘어 프로젝트의 스택, 인프라, 특정 패턴을 포함한 메모리를 제공함으로써 프로덕션 수준의 애플리케이션을 구축하는 방법을 다룹니다.
핵심 포인트
- AI는 일반적인 패턴을 따르므로 특정 프로젝트의 아키텍처를 이해하지 못함
- 컨텍스트 없는 세션은 아키텍처를 매번 재발명하게 만드는 비용 발생
- 전체 스택과 인프라를 담은 단일 참조 파일(Context File) 구축이 핵심
- 인증 헤더, 스토리지 래퍼 등 프로젝트 특화 규칙을 명시해야 함
만약 당신이 저와 비슷하다면
대부분의 개발자들은 이에 대응하여 부분적인 컨텍스트 (Context)를 제공합니다. 고장 난 컴포넌트를 붙여넣거나, 원하는 기능을 설명하거나, 에러 로그를 첨부합니다.
그러면 Claude는 최선을 다합니다. 이는 Claude가 고립된 상태에서는 합리적으로 보이고, 일반적인 패턴을 따르며, 당신이 이미 구축해 놓은 아키텍처 (Architecture)와는 아무런 상관이 없는 코드를 생성한다는 것을 의미합니다.
이것이 실제 상황에서 무엇을 의미하는지 생각해 보십시오. 당신이 고장 난 인증 (Auth) 함수를 붙여넣으면, Claude는 당신이 구축한 것과는 다른 세션 모델 (Session model), 백엔드 (Backend)가 기대하는 것과는 다른 토큰 패턴 (Token pattern), 그리고 당신의 미들웨어 (Middleware)가 절대 잡아낼 수 없는 헤더 이름 (Header name)을 사용하는 깔끔한 솔루션을 만들어냅니다. 이는 Claude가 틀렸기 때문이 아닙니다. Claude가 당신이 이미 구축한 특정 시스템이 아니라, 존재하지 않는 일반적인 앱을 위해 설계하고 있기 때문입니다.
문제는 당신의 프롬프트 (Prompts)가 아닙니다. 문제는 당신의 프롬프트에 메모리 (Memory)가 없다는 것입니다.
컨텍스트 없이 시작하는 모든 세션은 당신이 Claude에게 당신의 아키텍처를 처음부터 다시 발명하도록 비용을 지불하고 있는 세션입니다.
이에 대한 해결책이 있으며, 이는 제가 AI로 애플리케이션을 구축하는 것에 대해 말할 수 있는 가장 중요한 내용입니다. 하지만 그곳에 도달하기 위해서는 한 단계 더 깊이 들어가야 합니다.
II — 당신의 AI는 일반적인 앱을 만듭니다. 당신의 아키텍처는 구체적입니다.
우리가 Expo React Native에서 실행되는 크로스 플랫폼 생산성 앱, Catalyst 서버리스 (Serverless) 상의 커스텀 Express 백엔드, 그리고 ZCQL을 사용하는 Catalyst DataStore를 기반으로 TracE를 구축했을 때, 우리는 첫날부터 이 문제에 부딪혔습니다.
Claude는 정확히 맞아 보이는 컴포넌트들을 생성하곤 했습니다. 깔끔한 TypeScript, 적절한 훅 (Hooks), 합리적인 프롭 타입 (Prop types)까지 말이죠.
그러고 나서 그것들을 실제 앱에 연결하면 아무것도 작동하지 않았습니다. 코드가 나빴기 때문이 아닙니다. 코드가 다른 세상을 가정하고 있었기 때문입니다.
그것은 표준 Authorization 헤더를 사용하는 REST API를 가정했지만, 우리의 백엔드는 Catalyst의 게이트웨이가 표준 인증 헤더를 가로채는 것을 방지하기 위해 커스텀 X-TE-Token 헤더를 사용합니다. 그것은 토큰 저장을 위해 localStorage를 가정했지만, 우리 앱은 모바일과 웹 모두에서 실행되며 expo-secure-store는 웹에서 작동하지 않기 때문에 AuthContext.tsx에 플랫폼별 스토리지 래퍼(wrapper)를 두고 있습니다. 그것은 네이티브 모듈에 대해 import를 가정했지만, 정적으로 임포트된 네이티브 모듈은 OTA(Over-the-Air) 업데이트 시 앱을 충돌시킵니다.
이것들은 모호한 요구사항이 아닙니다. 단지 우리의 스택(stack)에 특화된 것들일 뿐입니다. 그리고 Claude는 이 중 어떤 것도 알 방법이 없었습니다.
돌파구는 우리가 원하는 것을 설명하는 것을 멈추고, Claude에게 작업할 수 있는 전체 시스템을 제공하기 시작했을 때 찾아왔습니다.
우리는 새로운 Claude 세션이 어떤 기능이든 즉시 구현하는 데 필요한 모든 것을 담은 단일 참조 파일인 tracE.md를 구축했습니다. 전체 스택과 인프라(infrastructure). 컬럼(column) 수준의 노트가 포함된 모든 데이터베이스 테이블. 인증 요구사항과 예상 페이로드(payload)가 포함된 모든 백엔드 라우트(route). 우리가 고생하며 배운 중요한 패턴과 주의사항(gotchas). 로컬 개발에서 프로덕션까지의 정확한 배포 워크플로우(workflow).
우리는 모든 Claude 세션의 맨 위
Layer 1 — 인터페이스 (The Interface). 누구나 이 단계까지는 도달합니다. Claude는 UI 제작에 탁월합니다. 작업 카드 컴포넌트(task card component), 날짜 선택기(date picker), 검색 오버레이(search overlay), 애니메이션 입력 바(animated input bar)를 만들어 달라고 요청하면, 90초 안에 멋진 결과물을 내놓을 것입니다. 이것이 바로 '바이브 코딩 (vibe coding)'이 명성을 얻은 지점입니다. 또한 대부분의 튜토리얼이 멈추는 지점이기도 합니다.
Layer 2 — 데이터 레이어 (The Data Layer). 상태 관리 (State management), 데이터 훅 (data hooks), 낙관적 업데이트 (optimistic updates), 폴링 (polling), 페이지네이션 (pagination). 여기서부터 상황이 어려워지기 시작합니다. Claude는 여전히 좋은 코드를 생성할 수 있지만, 결정 사항들이 상호 의존적이 됩니다. 데이터 훅이 어떻게 구조화되느냐에 따라 모든 컴포넌트의 새로고침 방식이 결정됩니다. 낙관적 업데이트를 어떻게 처리하느냐에 따라 UI가 즉각적으로 느껴질지 아니면 느리게 느껴질지가 결정됩니다. Claude가 고립된 상태에서 생성한 코드는 당신이 구축하려는 아키텍처(architecture)와 깔끔하게 통합되지 않을 것입니다. 컨텍스트(context)가 없는 세션은 여기서부터 실제 기술 부채 (technical debt)를 만들기 시작합니다.
Layer 3 — 프로덕션 레이어 (The Production Layer). 인증 (Auth), 백엔드 라우트 (backend routes), 배포 (deployment), 멀티 디바이스 세션 (multi-device sessions), 플랫폼별 동작 (platform-specific behavior), CORS, 환경 전환 (environment switching), 네이티브 (native) 대 OTA 업데이트 제약 사항. 이것이 대부분의 바이브 코딩 앱이 배포를 멈추는 지점입니다. 개발자가 포기했기 때문이 아니라, 컨텍스트 요구 사항이 일반적인 프롬프트(prompt)가 감당할 수 있는 수준을 넘어 폭발적으로 증가했기 때문입니다.
TracE의 TeSessions 문제를 예로 들어보겠습니다. 원래 우리는 TeUsers 테이블에 단일 sessionToken 컬럼을 가지고 있었습니다. 간단했습니다. 잘 작동했습니다. 웹 앱을 추가하기 전까지는 말이죠.
웹 로그인이 모바일 토큰을 덮어씌웠습니다. 브라우저에서 로그인하면 휴대폰에서 로그아웃되었습니다. 전형적인 멀티 디바이스 세션 문제로, 모든 진지한 인증 시스템이 해결하는 문제이지만, 이를 올바르게 수정하려면 당신의 특정 데이터 모델 (data model)을 이해해야 합니다.
해결책은 새로운 TeSessions 테이블을 만드는 것이었습니다. 활성 세션당 하나의 행을 생성하고, 기기당 고유한 토큰을 부여하며, 사용자 레코드 대신 해당 테이블을 쿼리하는 미들웨어(middleware)를 사용하는 방식입니다. 구조를 파악하고 나면 매우 간단합니다. 하지만 기존 스키마 (schema) 없이 Claude에게 이 문제를 설명했다면, 이미 구축해 놓은 것과 일치하지 않는 일반적인 세션 관리 구현 방식을 제안받았을 것입니다.
전체 스키마를 컨텍스트 (context)에 포함하자, Claude는 문제를 진단하고, TeSessions 테이블을 제안하고, 마이그레이션 (migration)을 작성하고, 인증 미들웨어를 업데이트하며, 로그인 및 로그아웃 라우트 (routes)를 수정하는 과정을 단 한 번의 교환으로 수행했습니다.
레이어 3 (Layer 3)은 개발자가 실패하는 지점이 아닙니다. 컨텍스트가 없는 개발자가 실패하는 지점입니다.
IV — 아키텍처 파일은 에이전트 기반 개발 (agentic development)의 열쇠입니다
"사이버네틱스 (Cybernetics): 원하는 것을 얻는 기술."
— 노버트 위너 (Norbert Wiener)
엔지니어링에서 피드백 제어 시스템 (feedback control system)은 두 가지, 즉 명확한 목표와 현재 상태에 대한 정확한 모델 (model)이 있어야만 목표를 향해 항해할 수 있습니다. 선박의 오토파일럿 (autopilot)은 단순히 어디로 가고 있는지만 아는 것이 아니라, 매 순간 선박의 현재 침로, 속도, 위치를 알고 있습니다. 그 모델이 없다면 수정 작업은 무작위로 이루어집니다. 하지만 모델이 있다면 모든 조정은 목적을 갖게 됩니다.
Claude도 같은 방식으로 작동합니다.
컨텍스트 없이 Claude에게 프롬프트 (prompt)를 입력하는 것은, 매우 유능한 시스템에게 현재 위치에 대한 모델도 없이 목표를 향해 항해하라고 요청하는 것과 같습니다. 출력 결과는 당신의 특정 시스템이 아닌 일반적인 모범 사례 (best practices)를 반영할 것입니다. 겉보기에는 맞을지 몰라도 통합 (integrate) 과정에서 오류가 발생할 것입니다.
Claude에게 완전한 아키텍처 파일을 제공하면, 당신은 목표와 모델을 모두 제공하는 셈이 됩니다. 목표는 당신이 설명하는 기능이나 수정 사항이며, 모델은 스택 (stack), 스키마 (schema), 패턴 (patterns), 제약 조건 (constraints), 알려진 주의 사항 (gotchas)을 포함한 시스템의 전체 상태입니다. 이제 모든 출력은 추측이 아닌, 정밀하게 조정된 수정 사항이 됩니다.
유용한 아키텍처 파일이 실제로 포함해야 할 내용은 다음과 같습니다:
스택 레이어 (The stack layer). 단순히 "React Native와 Node"라고 적는 것이 아닙니다. 정확한 SDK 버전, 빌드 도구, 서버리스 플랫폼 (serverless platform), 웹 앱이 서빙되는 방식(TracE의 경우: 동일한 Catalyst 함수 내부에서 정적 파일로 서빙되며, 이는 CORS를 완전히 제거하는 동일 출처 아키텍처 (same-origin architecture)입니다)을 명시해야 합니다. 프로덕션 (production), 개발 (development), 로컬 (local) 환경을 위한 URL도 포함합니다.
데이터베이스 스키마 (The database schema). 모든 테이블, 모든 컬럼, 그리고 결정적으로 명확하지 않은 결정 뒤에 숨겨진 *이유 (why)*를 기록합니다. TeSessions가 존재하는 이유는 trace.md에 다음과 같이 기록되어 있습니다: "TeUsers는 단일 sessionToken 컬럼을 가지고 있었는데, 웹 로그인이 모바일의 토큰을 덮어써서 로그아웃되는 문제가 발생했습니다." 이 한 문장이 Claude가 인접한 코드를 리팩터링 (refactor)할 때 이전 패턴으로 회귀하는 것을 방지합니다.
API 계약 (The API contract). 모든 라우트 (route), 메서드 (method), 인증 (auth) 필요 여부, 예상되는 페이로드 (payload)를 정의합니다. Claude가 /auth/login을 호출하는 프론트엔드 코드를 생성할 때, 응답에서 X-TE-Token을 보내고 헤더에서 이를 기대해야 한다는 것을 아는 이유는 추측했기 때문이 아니라, 계약이 파일에 명시되어 있기 때문입니다.
핵심 패턴 (The critical patterns). 명확하지 않은 결과를 초래하는 결정 사항들입니다. TracE의 경우: 네이티브 모듈은 정적 임포트 (static imports) 대신 try { require(...) } catch를 사용합니다. 정적 임포트는 OTA (Over-the-Air) 업데이트 시 크래시를 일으키기 때문입니다. ZCQL는 300행 제한이 있으며 페이지네이션 (pagination)이 필요합니다. Catalyst 백엔드에서 오는 날짜 문자열은 밀리초 앞에 T가 아닌 공백 구분자를 사용하며, 이는 표준 ISO 파싱을 깨뜨립니다. 이것들은 예외 케이스 (edge cases)가 아니라, Claude가 관련 코드를 건드리기 전에 반드시 알아야 하는 시스템의 사실 (facts)입니다.
주의 사항 (The gotchas). 여러분이 이미 저지른 실수들입니다. TracE에서는 Zoho ZGS 게이트웨이가 배포된 함수에 대해 CORS 프리플라이트 (preflight) 헤더를 제거하여, 브라우저에서 Slate URL을 완전히 사용할 수 없게 만든다는 점을 아키텍처 파일에 기록했습니다. Catalyst의 ZCQL에서 빈 문자열 (empty strings)은 ??로 잡을 수 없으며 ||를 사용해야 한다는 점도 기록했습니다. 브라우저에서 로그인 시 "Failed to fetch"가 표시된다면 가장 먼저 catalyst serve가 실행 중인지 확인해야 한다는 점도 기록했습니다.
이것들은 인상적인 기술적 사실들이 아닙니다. Claude가 올바른 코드를 생성하느냐, 아니면 프로덕션 (production) 환경에서 작동하지 않는 그럴듯해 보이는 코드를 생성하느냐의 차이를 만드는, 구체적이고 어렵게 얻어낸 지식들입니다.
이 파일을 한 번 구축하십시오. 시스템이 진화함에 따라 업데이트하십시오. 이는 복리로 쌓입니다.
V — 풀스택 프롬프팅 프로토콜: 아이디어에서 프로덕션까지
이것은 제가 오늘 Claude를 사용하여 새로운 애플리케이션을 시작한다면, TracE를 구축하며 배운 모든 것을 활용하여 따를 프로토콜입니다.
이 프로토콜은 단계별로 구조화되어 있는데, 그것이 실제 풀스택 개발이 작동하는 방식이기 때문입니다. 단계가 엄격하게 고정된 것은 아니지만, 순서가 중요합니다.
1단계 — 아키텍처 우선 (Architecture First)
애플리케이션 코드를 단 한 줄이라도 작성하기 전에, Claude와 함께 아키텍처 파일을 구축하는 데 한 세션(session)을 할애하십시오.
앱, 앱이 해결하는 문제, 그리고 사용자에 대해 설명하십시오. 인프라 제약 사항을 설명하십시오. 우리의 경우 이미 사용 가능했던 Catalyst를 예로 들었지만, 이는 Vercel, Railway 또는 Fly에도 동일하게 적용됩니다. Claude가 스택 (stack)을 제안하도록 하십시오. 불필요한 복잡성을 초래하는 모든 것에 대해 반대 의견을 내십시오. 그런 다음 결정 사항을 문서화하십시오.
훌륭한 아키텍처 세션은 다음과 같은 결과물을 만들어냅니다: 버전 정보가 포함된 스택, 테이블 및 컬럼의 근거가 포함된 데이터베이스 스키마 (database schema), 라우트 수준의 계약 (route-level contracts)이 포함된 API 표면 (API surface), 인증 모델 (auth model), 배포 파이프라인 (deployment pipeline), 그리고 알려진 첫 번째 제약 사항 세트입니다.
1단계의 결과물은 코드가 아닙니다. 그것은 이후의 모든 코드를 올바르게 만드는 참조 파일 (reference file)입니다.
2단계 — 계층별 구축 (Layer by Layer)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기