
바이브코딩(vibecoding)을 통해 AI 채용 검색 대시보드를 30회 이상 출시하며 얻은 원칙
요약
Claude Code를 활용한 '바이브코딩(vibecoding)' 방식으로 AI 채용 대시보드를 구축하며 얻은 실전 원칙을 공유합니다. 30회 이상의 릴리스와 대규모 테스트를 통해 프로덕션 수준의 코드베이스를 유지하기 위한 핵심 규칙을 다룹니다.
핵심 포인트
- 릴리스당 단 하나의 논리적 수정 사항만 배포할 것
- 다국어 지원(9개 로케일)을 위한 변경 이력 관리 필수
- 바이브코딩을 프로덕션 등급으로 확장하기 위한 원칙 수립
- AI 기반 채용 파이프라인의 시각적 UI 구축 경험
저는 Sergey입니다. 지난 몇 달 동안 저는 Claude Code 바이브코딩 (vibecoding) 세션을 통해 채용 지원을 관리하기 위한 로컬 전용 브라우저 대시보드인 career-ops-ui를 완전히 구축했습니다. 30회 이상의 릴리스 (releases), 9개의 로캘 (locales), 1,000개 이상의 유닛 테스트 (unit tests), 70개 이상의 Playwright 케이스, MIT 라이선스로 배포되었습니다.
이 포스트는 바이브코딩 (vibecoding)이 실제로 프로덕션 등급의 코드베이스 (codebase)로 확장될 수 있게 만든 **원칙 (doctrine)**과, 이를 거의 망칠 뻔했던 **실수하기 쉬운 요소들 (footguns)**에 대한 기록입니다. 마케팅적인 내용은 건너뛰겠습니다. 저는 충분한 실패를 겪은 후, 그 중요성을 깨닫고 명문화한 규칙들을 공유하고자 합니다.
내가 만든 것
career-ops (4일 만에 GitHub 별 27K개를 달성한 AI 채용 검색 파이프라인)를 기반으로 한 셀프 호스팅 웹 UI입니다. 원본은 CLI 전용입니다. 저는 매주 100개 이상의 채용 공고를 처리하고 있었는데, 터미널을 통해 분류하는 작업이 제 저녁 시간을 갉아먹고 있었기에 시각적 레이어를 구축했습니다:
- JD URL 붙여넣기 → AI가 A-F 점수 부여 → 맞춤형 이력서 생성
- 수백 개의 공고를 위한 시각적 파이프라인 (필터링, 정렬, 인라인 중복 제거)
- 퍼널 (funnel) 기능이 포함된 지원 추적기 (지원함 → 인터뷰 → 오퍼)
- 실시간 마크다운 (markdown) 미리보기 및 PDF 내보내기가 가능한 CV 에디터
- 심층 기업 조사 모드 (상위 프롬프트가 이탈할 경우 짧은 경고 포함)
- STAR+R 프레워크를 사용한 인터뷰 준비
- URL별 영속성 (persistence)을 가진 지원 체크리스트
- 급여 범위 필터 (통화에 구애받지 않으며, NBSP 인식 가능)
- 12개 소스에 대한 실시간 SSE 스캐너 (Greenhouse, Ashby, Lever, Workday, hh.ru, Habr Career, Trudvsem, RSS 등)
- 9개 로캘 (locales): en, es, pt-BR, fr, ru, ja, ko, zh-CN, zh-TW
- 제공자 중립적인 LLM 라우팅 (Anthropic / Gemini / OpenAI / Qwen / OpenRouter)
127.0.0.1:4317에서 실행, MIT 라이선스, 가입 불필요, 텔레메트리 (telemetry) 없음
바이브코딩 원칙 (살아남은 5가지 규칙)
1. 릴리스당 단 하나의 수정 사항만. 절대 묶어서 처리하지 말 것.
모든 릴리스는 정확히 하나의 논리적 변경 사항만을 배포합니다. 우선순위는 HIGH → MEDIUM → LOW 순입니다. 각 배포에는 다음 사항이 포함됩니다:
- 버전 업 (Version bump)
- **9개 모든 로케일 (locales)**에서의 CHANGELOG 항목 작성 (동등성 게이트 적용)
- 수정 전에는 반드시 실패해야 하는 전용 회귀 잠금 테스트 (regression-lock test)
- 특정 영역에 대한 Playwright 검증
- Pre-commit AI 리뷰 승인
- Node 18 / 20 / 22 및 Playwright e2e 환경에서의 CI 통과 (green)
느려 보일 수 있습니다. 하지만 실제로 배치(batching) 처리보다 더 빠릅니다. 버그의 원인을 명확히 파악할 수 있고, 롤백(rollback)이 매우 간단하며, AI가 한 세션 동안 우리가 무엇을 배포하고 있는지에 대한 컨텍스트(context)를 놓치지 않기 때문입니다.
2. TDD-first는 "RED BAR MANDATORY(레드 바 필수)"를 의미합니다
가장 큰 교훈은 실제로 해결되기 전까지 다섯 번이나 재발했던 회귀(regression) 문제에서 얻었습니다. 이전의 모든 "해결" 단계에서는 테스트가 통과 상태였습니다:
// 테스트가 단언(assert)했던 내용 (잘못됨)
test('scroll-spy implementation present', () => {
const file = readFileSync('public/js/views/help.js', 'utf-8');
...
하지만 사용자에게 보이는 동작은 망가져 있었습니다. 스크롤 시 활성화된 목차(TOC) 항목이 전혀 하이라이트되지 않았습니다. 테스트가 동작(behavior)이 아닌 소스 코드의 **형태 (shape)**를 확인했기 때문입니다.
6번째 시도에서 저는 스스로에게 다음 과정을 강제했습니다:
- 실패하는 Playwright 테스트를 가장 먼저 작성한다.
- 이를 브랜치에 커밋(commit)한다.
- 푸시(push)한다.
- PR(Pull Request)에서 레드 바(red bar, 테스트 실패 상태)를 스크린샷으로 찍는다.
- 그다음에만 수정 코드를 작성한다.
// 테스트가 단언했어야 하는 내용
test('TOC scroll-spy highlights active section', async ({ page }) => {
await page.goto('http://127.0.0.1:4317/#/help');
...
이 사이클을 통해 버그를 단 한 번에 해결했습니다.
교훈: AI는 기존의 망가진 코드에 대해 통과하는 테스트를 기꺼이 작성해 줄 것입니다. 수정 코드가 저장소(repo)에 닿기 전에 반드시 레드 바가 보이도록 강제해야 합니다.
3. 방법론적 실수(Footguns)는 문서화한다
수많은 가짜 음성(false-negative) 승인을 거친 후, 저는 QA 프롬프트에 §−1 Footguns 섹션을 만들기 시작했습니다. 제가 가장 크게 겪었던 세 가지는 다음과 같습니다:
Footgun A: 파일 경로 단언(file-path assertions) vs 동작 단언(behavior assertions). 실제 구현에서는 기존 파일에 내용을 인라인(inline)으로 포함시켰음에도 불구하고, 저는 제안된 추출 파일이 존재하는지를 단언(assert)했습니다. git grep mountHelpToc public/ 결과는 0을 반환했습니다. 이는 회귀(regression)가 아니라, 잘못된 조사(probe)였습니다. 파일 경로가 아니라 동작(behavior)(클래스가 적용되었는가? 요소가 그려졌는가?)을 단언하세요.
Footgun B: 클라이언트 측 URL 정규화(URL normalization). fetch()와 curl(--path-as-is 옵션 미사용 시)은 전송 전에 URL을 정규화합니다. 따라서 서버의 가공되지 않은 .. 경로 탐색 방어 기제(traversal guard)를 결코 실행하지 않습니다. 미들웨어(middleware)를 검증하려면 다음과 같이 하세요:
curl -s --path-as-is 'http://127.0.0.1:4317/api/jds/../../../etc/passwd'
# 예상 결과: {"error":"invalid path"}
Footgun C: vm-realm deepEqual. node:vm 내부에서 생성된 객체는 **외부 프로토타입(foreign prototype)**을 가집니다. JSON 스냅샷(snapshot)에 대해 assert.deepStrictEqual을 실행하면 값이 동일하더라도 실패합니다. 먼저 라운드 트립(round-trip)을 거치세요:
const snapshot = JSON.parse(JSON.stringify(assembledInVm));
assert.deepStrictEqual(snapshot, expected); // 이제 작동함
이러한 사항들을 문서화함으로써 이후 세션에서 발생할 수 있었던 수많은 오탐(false-positive) 디버깅 시간을 절약할 수 있었습니다. 새로운 AI 세션은 먼저 §−1 섹션을 읽고 올바른 조사(probe) 기술을 적용합니다.
4. 모든 것에 대한 로케일 인식 (I18N-SPLIT 아키텍처)
원래 9개의 모든 로케일(locale)은 하나의 36KB JS 딕셔너리에 들어 있었습니다. 한 커뮤니티 기여자(Discord의 Mike)가 다음과 같이 지적했습니다:
".po 파일이 번역가들에게 적합할 것입니다. 현재 설정은 매우 고통스럽습니다."
그의 말이 맞았습니다. 저희는 공유 키를 위한 @alias 메커니즘을 갖춘 로케일별 파일(per-locale files) 방식으로 리팩터링(refactor)했습니다:
public/js/lib/locales/
i18n-dict.en.js // window.__I18N_DICT_EN = { ... }
i18n-dict.es.js
...
index.html에서의 로드 순서는 다음과 같습니다: 9개의 로케일 파일 → 에일리어스(aliases) → 어셈블러(assembler) → i18n.js. t() 함수는 전혀 변경되지 않았습니다. 호출 지점(call-sites)을 단 한 곳도 수정하지 않았습니다.
이 아키텍처의 유효성은 두 달 후 프랑스어가 추가되었을 때 입증되었습니다. 당시 커뮤니티 기여자가 로컬 Ollama와 번역을 위한 qwen2.5:14b를 사용하여 작업했습니다.
// 기여자에 의해 호출되는 translateChunk()
async function translateChunk(chunk) {
const payload = Object.fromEntries(chunk.map(c => [c.key, c.english]));
...
이와 동일한 워크플로우는 이제 메인 사전(dict)을 건드리지 않고도 10번째, 11번째 로컬 언어(locale)로 확장 가능합니다. 무료 로컬 추론 (Free local inference), 조정 비용 제로 (zero coordination cost).
5. 읽기 전용 경계 테스트(Read-only boundary tests)가 파괴적인 AI 제안을 잡아냅니다
부모 프로젝트인 career-ops는 변경 가능한(mutable) 사용자 데이터입니다. 웹 UI에는 엄격한 규칙이 있습니다: 명시적인 사용자 작업(Pipeline +Add, CV Save, Config write)을 제외하고는 절대 부모 파일에 쓰지 마십시오.
모든 테스트는 CAREER_OPS_ROOT가 mktemp -d를 가리키도록 설정된 상태에서 실행됩니다:
// tests/setup.mjs
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
...
만약 어떤 테스트라도 실제 부모 파일에 쓰기를 시도하면 즉시 실패합니다. 이를 통해 AI 보조 코드 리뷰 (AI-assisted code review)를 매우 안전하게 만들 수 있습니다. Claude가 어떤 리팩터링(refactor)을 제안하더라도, 경계 테스트(boundary tests)가 부모 파일의 변조(mutation)를 자동으로 잡아냅니다.
효과가 없었던 것들
- 수정 사항 일괄 처리 (Batching fixes)는 "이번 한 번만"을 초래함 — 교리 예외(doctrine-exception)가 포함된 모든 릴리스 이후에는 반드시 후속 수정 사항(fix-ship)이 필요했습니다. 순수하게 오버헤드(overhead)일 뿐입니다.
- 한 번의 프롬프트에 "구현 및 테스트 작성"을 포함하는 것 — 깨진 코드에 대해서도 통과되는 해피 패스(happy-path) 테스트만을 생성합니다. 분리하십시오: 실패하는 테스트 작성 → 레드(red) 상태 확인 → 그 후에만 코드 작성.
npm test 2>&1 | grep—grep은 일치하는 항목이 있으면 0을 반환하여 종료 코드(exit code)를 가려버립니다.npm test를 실행하고,$?를 캡처한 다음에grep을 사용하십시오.- 행동적 약속(behavioral promises)에 대한 정적 잠금 테스트 (Static lock-tests) — 심볼(symbol)을 찾기 위한
git grep은 사용자에게 보이는 동작이 제대로 작동한다는 것을 증명하지 못합니다.
나를 놀라게 했던 것들
- 커뮤니티가 예상보다 더 강력하게 프로젝트를 끌어올렸습니다. Mike는 i18n (국제화) 아키텍처 문제를 지적했고, 프랑스인 기여자는 로컬 Ollama를 사용하여 48시간 만에 전체 딕셔너리 (dict)를 번역했습니다. 조정 비용이 전혀 들지 않았습니다.
- 9개 로케일(locales) × 19개 라우트(routes) × 75개 H3 섹션은 테스트 가능합니다. 매 사이클마다 동일한 9개 로케일 × 라우트 스윕 (route-sweep) 자동화 테스트가 실행됩니다. 작성하는 데 30분이 걸렸지만, 매 출시마다 수 시간을 절약해 줍니다.
- 바이브코딩 (vibecoding)은 아키텍처 수준의 결정으로 확장됩니다. I18N-SPLIT 리팩토링은 12시간 동안 진행된 Claude Code 세션이었습니다. AI는 8개의 로케일 파일을 검토하고,
@alias패턴을 제안하고, 마이그레이션 스크립트를 작성하고, 스냅샷을 재생성하고, 패리티 테스트 (parity tests)를 푸시했습니다 — 이 모든 것이 단 한 번의 세션에서 이루어졌습니다.
스택 (Stack)
- 백엔드 (Backend): Node.js 18+ Express 서버, 약 130 LOC의
server/index.mjs오케스트레이터(orchestrator) + 15개 라우트 모듈 - 프론트엔드 (Frontend): 프레임워크 없음, 바닐라 JS (vanilla JS), 해시 라우터 (hash-router) SPA
- 프로덕션 의존성 (Prod deps):
express + js-yaml + multer만 사용 - 테스트 (Tests):
node --test(1000개 이상의 유닛 테스트), 70개 이상의 Playwright 케이스, 23개의 포괄적인 e2e 테스트 - LLM 라우팅 (LLM routing): Anthropic / Gemini / OpenAI / Qwen / OpenRouter (설정된 키가 있는 곳으로 자동 라우팅; 키가 없는 경우에도 수동 폴백 (fallback) 작동)
- i18n (국제화): 로케일별 9개의 딕셔너리 (dict) 파일 +
@alias메커니즘, 서버 측 영어 폴백 (fallback) - 보안 (Security):
unsafe-inline/unsafe-eval이 없는 CSP, SSRF 가드, CV 유입 시stripDangerousMarkdown()적용, 마스킹된 비밀값 (secrets),/api/*에 대한 JSON-404 - 스트리밍 (Streaming): 긴 작업(스캔, 자동 파이프라인, 배치)을 위한 서버 전송 이벤트 (Server-Sent Events)
- 데이터 (Data): 모든 사용자 상태(CV, 지원서, 보고서)를 위한 Markdown — 버전 관리 가능,
cat가능, 독점 형식을 사용하지 않음
다른 바이브코딩 빌더들을 위한 교훈 (Takeaways)
- 실패하는 테스트를 먼저 작성하고, 빨간색 바(red bar)를 스크린샷으로 찍어두세요. AI는 망가진 코드에 대해 기꺼이 통과하는(green) 테스트를 만들어낼 것입니다.
- 방법론상의 실수(footguns)를 발견할 때마다 기록하세요. 다음 세션에서 이를 먼저 읽는 것만으로도 몇 시간의 시간을 아낄 수 있습니다.
- 릴리스당 하나의 수정(One-fix-per-release) 방식은 놀라울 정도로 잘 확장됩니다. 버그의 원인을 파악하기 쉽고, 롤백(rollback)이 간단하며, AI가 문맥(context)을 놓치지 않습니다.
- 언어별 파일 단위의 국제화(i18n) 아키텍처는 그만한 가치가 있습니다. 커뮤니티 기여자들은 단일 파일을 포크(fork)할 수 있어 온보딩(onboarding)이 수월합니다.
- 읽기 전용 경계 테스트(Read-only boundary tests)는 파괴적인 AI 제안을 잡아냅니다. AI는 무엇이든 제안할 수 있지만, 테스트는 계약(contract)을 강제합니다.
시도해보기
git clone https://github.com/Fighter90/career-ops-ui
cd career-ops-ui
npm install
...
무료이며, MIT 라이선스이고, 가입이 필요 없으며, 로컬에서 실행됩니다. AI 키는 선택 사항입니다 (수동 프롬프트 모드에서도 작동합니다).
- GitHub: Fighter90/career-ops-ui
- LinkedIn (대화 환영): sergey-emelyanov-in-job
dev.to 커뮤니티에 드리는 질문: 여러분이 바이브코딩(vibecoding)을 통해 얻은 가장 값진 교훈은 무엇인가요? 특히 AI가 자신 있게 망가진 솔루션을 제안했을 때, 다른 빌더들이 어떤 원칙(doctrines)을 통해 이를 해결했는지 궁금합니다. 아래에 댓글을 남기거나 여러분만의 아키텍처 교훈을 공유해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기