바이브코더를 위한 Cloudflare막히는 순간을
푸는 1시간

Byung Kim, Daheyo Works

강의 자료 QR 코드강의 자료는 여기learn.daheyo.com/cloudflare-1h

손 들어 주세요

  1. AI로 뭔가 만들어서 내 컴퓨터에서 돌려본 분?
  2. 그걸 친구에게 링크로 보내 본 분?
  3. 그러다 API 요금이나 에러 때문에 멈춘 분?

오늘의 한 문장

AI는 코드는 잘 써 준다.
코드 바깥은 대신 해 주지 못한다.

배포, 비밀 키, 데이터, 로그인, 운영. 오늘은 이 다섯 칸을 차례로 채웁니다.

배포파트 1 비밀 키파트 2 데이터파트 3 로그인파트 4 운영파트 5 코드AI가 써 준다

오늘 고칠 앱: AI 메모장

메모를 쓰면 AI가 요약해 주는 HTML 페이지 하나. AI가 만들어 준 그대로입니다.

지금 상태무엇이 문제인가고치는 곳
localhost에서만 돈다친구에게 링크를 보낼 수 없다파트 1
API 키가 JavaScript에 있다누구나 키를 복사해 내 돈으로 AI를 쓴다파트 2
새로고침하면 메모가 사라진다데이터가 브라우저 안에만 있다파트 3
로그인이 없다주소만 알면 남의 메모를 본다파트 4
고장 나면 알 길이 없다"안 돼요"라는 말만 듣는다파트 5

파트 1. 배포의 벽

친구한테 보낸 링크가
localhost:3000이었다.

localhost는 내 컴퓨터 안에서만 통하는 주소입니다.

파트 1. 원리

배포 = 24시간 켜진
남의 컴퓨터에 올려 두기

파트 1. 시연

wrangler.jsonc
{
  "name": "ai-memo",
  "compatibility_date": "2026-09-30",
  "main": "./src/worker.ts",
  "assets": {
    "directory": "./dist",
    "not_found_handling": "single-page-application",
    "run_worker_first": ["/api/*"]
  },
  "observability": { "enabled": true }
}
터미널
npx wrangler login
npx wrangler deploy

출력된 주소를 직접 열어 보는 것까지가 배포.

내 도메인은 네임서버를 Cloudflare로 옮긴 뒤 커스텀 도메인으로 붙입니다. DNS와 HTTPS는 Cloudflare가 만듭니다.

파트 1. 여기서 많이 막힙니다

증상원인과 해결
빈 화면assets.directory가 빌드 결과 폴더(dist 등)가 아니다
새로고침하면 404SPA는 not_found_handling을 직접 적어야 한다
도메인이 안 붙음네임서버가 Cloudflare에 있어야 한다. www는 따로 등록
"성공"인데 안 바뀜같은 이름의 Pages 프로젝트로 올라간 실제 사례. 주소를 직접 열어 보기

한국에서 느리다면 내주소/cdn-cgi/trace의 colo 값부터 확인. 자세한 내용은 자료 페이지 파트 1.

파트 2. 키 노출과 비용 폭탄

F12를 눌렀더니
내 API 키가 그대로 보였다.

방금 이 키를 전 세계에 공개했습니다. 노출된 키는 폐기하고 새로 만드세요.

파트 2. 원리

고치기 전

브라우저JavaScript 안에 API 키
직접 호출
AI APIOpenAI, Anthropic 등

F12만 누르면 누구나 키를 복사합니다.

고친 뒤

브라우저/api/summarize만
요청
내 Worker① 잦은 요청 거르기
② 시크릿 키 붙이기
대신 호출
AI API

키는 Worker 시크릿 안에만 있습니다.

파트 2. 시연

키 등록: 바로 배포까지 된다
npx wrangler secret put AI_API_KEY
.gitignore
.dev.vars*
.env*

로컬 개발용 키는 .dev.vars에. 저장소에는 올리지 않습니다.

wrangler.jsonc: 레이트 리밋
"ratelimits": [{
  "name": "LIMITER",
  "namespace_id": "1001",
  "simple": { "limit": 10, "period": 60 }
}]
Worker
const { success } = await env.LIMITER
  .limit({ key: ip });
if (!success) return new Response('잠시 후에',
  { status: 429 });

파트 2. 돈은 어디서 새는가

무료 플랜은 멈춘다

Workers 하루 10만 요청. 한국 시간 오전 9시 초기화. 넘으면 과금이 아니라 1027 오류.

유료 플랜은 안 멈춘다

월 $5부터. 자동으로 멈추는 상한은 공식 문서에 없음. Budget alert는 다음 날 오는 이메일.

진짜 폭탄은 AI API

OpenAI·Anthropic 콘솔에서 월 사용 한도를 꼭 걸 것.

레이트 리밋은 계량기가 아니라 폭주 차단기입니다. 데이터센터마다 따로 세고, 기간은 10초나 60초뿐입니다.

파트 2. AI가 자주 틀리는 것

그래서 마지막에 F12와 빌드 결과 폴더를 직접 확인합니다.

파트 3. 데이터가 사라진다

메모를 세 개 썼는데
새로고침하니 다 사라졌다.

localStorage로 고쳐도 휴대폰에서 열면 없습니다. 브라우저 저장은 그 기기, 그 브라우저에만 남습니다.

파트 3. 데이터마다 서랍이 다르다

저장소비유이럴 때무료 한도
D1엑셀 시트목록, 검색, 사용자별 데이터하루 쓴 행 10만, 읽은 행 500만
KV이름표 메모지설정, 캐시하루 쓰기 1,000, 읽기 10만
R2창고이미지, PDF, 업로드 파일월 저장 10GB, 전송 무료

KV에 사용자 데이터를 넣지 마세요. 다른 지역에 보이기까지 60초 넘게 걸릴 수 있습니다. "저장이 됐다 안 됐다 해요"의 정체입니다.

파트 3. 시연

터미널
npx wrangler d1 create memo-db

npx wrangler d1 migrations create \
  memo-db create_notes

npx wrangler d1 migrations apply \
  memo-db --remote
Worker: 입력은 bind()로
await env.DB
  .prepare('INSERT INTO notes
    (content, summary, created_at)
    VALUES (?1, ?2, ?3)')
  .bind(content, summary, now)
  .run();

이미 적용한 마이그레이션 파일은 고치지 않는다. 바꿀 게 생기면 새 파일을 만듭니다.

파트 3. 기억할 세 가지

--remote

빼면 로컬 DB에만 적용됩니다. 배포 후 "no such table"의 거의 전부.

${ 금지

SQL 문자열에 입력을 이어 붙이면 인젝션 구멍. AI 코드에서 찾아 .bind()로.

훑은 행

D1은 돌려준 행이 아니라 훑은 행을 셉니다. 자주 거르는 컬럼엔 인덱스.

파트 4. 로그인 붙이기

내 메모가 주소를 아는
모든 사람에게 보인다.

"로그인 만들어줘"의 결과물은 자주 위험합니다. 2026년 9월, 권장값대로 만든 비밀번호 저장 코드가 Workers 상한에 걸려 가입이 실패한 사례가 여러 건 나왔습니다.

파트 4. 원리: 문지기를 세운다

방문자앱 주소로 접속
먼저 검사
Cloudflare Access허용된 사람인가?
아니오
로그인 화면메일로 받은 일회용 코드
예
내 Worker이메일 확인 → 내 메모만

로그인 화면, 이메일 인증, 세션을 대시보드 설정만으로 얻습니다. Zero Trust 무료 플랜으로 소규모 팀까지.

파트 4. 시연

  1. Workers & Pages → 내 Worker → Access 탭 → Protect this Worker behind Access
  2. All traffic을 고른다. Previews only는 미리보기만 잠그고 실제 서비스는 열어 둡니다.
  3. 정책: Cloudflare account 또는 Email domain. Email domain에 gmail.com을 넣으면 전 세계 Gmail 사용자가 통과합니다.
  4. 시크릿 창 → 이메일 → 메일로 온 코드 → 입장
  5. 메모 테이블에 user_email 컬럼, 레이트 리밋 키도 이메일로

파트 4. 함정

정적 자산을 함께 서빙하는 Worker에는
ctx.access가 오지 않는다

공식 문서에 명시된 제약입니다. 대신 Cf-Access-Jwt-Assertion 헤더의 토큰을 검증합니다.

파트 4. 검증 코드 (jose)

src/auth.ts
let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;

export async function userEmail(request: Request, env: Env) {
  const token = request.headers.get('cf-access-jwt-assertion');
  if (!token) return null;
  jwks ??= createRemoteJWKSet(new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`));
  try {
    const { payload } = await jwtVerify(token, jwks, {
      issuer: env.TEAM_DOMAIN, audience: env.POLICY_AUD,
    });
    return typeof payload.email === 'string' ? payload.email.toLowerCase() : null;
  } catch { return null; }
}

파트 4. Access가 맞는 경우

상황추천
나만, 팀만, 지인 베타Access
공개 서비스의 관리자 페이지만경로 단위 Access 앱 + workers.dev 닫기
누구나 가입하는 서비스전용 인증 서비스 (Clerk, Supabase Auth, Auth0 등)

어느 쪽이든 인증 코드는 직접 짜지 않습니다.

파트 5. 출시 후 운영

사용자는 "안 돼요"라고만 하고,
나는 뭐가 왜 안 되는지 모른다.

AI에게 "디자인 좀 바꿔줘"라고 했다가 저장 기능이 깨진 버전을 배포했습니다.

파트 5. 습관 세 가지

로그로 본다

설정 파일에 observability. 지금 요청은 wrangler tail, 지난 에러는 대시보드. 에러 한 줄을 AI에게.

되돌린다

wrangler rollback은 코드만 되돌린다. 데이터는 D1 Time Travel(무료 7일, 유료 30일).

커밋한다

잘 동작하면 커밋, 커밋하고 배포. "지금 상태 커밋해 줘"면 충분.

파트 5. 시간이 남으면

에러 묶음을 에이전트에게 (Issues)

같은 원인의 에러를 한 묶음으로 보여주고 Claude Code, Cursor 등에 넘깁니다. catch로 삼킨 에러는 console.error를 불러야 잡힙니다.

폼 스팸 막기 (Turnstile)

무료. 위젯만 붙이면 보호되지 않습니다. Worker에서 Siteverify로 토큰을 검증해야 합니다. 토큰은 300초, 한 번만.

한 시간 동안 바뀐 것

시작끝
주소localhost공개 주소, 내 도메인
API 키브라우저에 노출Worker 시크릿
남용 방지없음이메일 기준 레이트 리밋
메모새로고침하면 사라짐D1에 저장
접근누구나Access 로그인, 사용자별 메모
문제 대응"안 돼요"로그, 롤백, Git

AI가 코드를 쓰게 하고,
나는 코드 바깥을 책임진다.

배포, 비밀, 데이터, 로그인, 운영. Cloudflare는 그 바깥을 한 곳에서 처리하게 해 줍니다.

과제

내 프로젝트 하나를
배포 전 체크리스트에 통과시키기

막히면 자료 페이지의 해당 파트 프롬프트를 그대로 AI에게 주세요. 명령어와 프롬프트에 복사 버튼이 있습니다.

강의 자료 QR 코드강의 자료learn.daheyo.com/cloudflare-1h