바이브코딩 · 배포

로컬에선 되는데 배포하면 에러? 바이브코딩 배포 오류 7가지 해결법

Vercel에 올리는 순간 터지는 오류는 생각보다 종류가 적다. 증상 7가지, 각각 어디 로그를 열고 AI에게 무엇을 붙여넣을지 정리했다.

초록 체크 표시가 뜬 노트북과 빨간 경고 표시가 뜬 클라우드 서버가 점선 배포 화살표로 이어진 아이소메트릭 일러스트

components/UserCard.tsx라는 파일을 import UserCard from './usercard'로 불러오면 어떻게 될까. 맥이나 윈도우 노트북에서는 아무 일도 없다. Vercel에서는 빌드가 멈춘다. Vercel 공식 가이드가 'module not found' 오류의 대표 원인으로 드는 바로 그 예시다.

바이브코딩 배포 오류의 대부분은 이런 식이다. 코드가 틀렸다기보다, 내 컴퓨터가 너무 관대했다. 로컬은 대소문자를 봐주고, .env.local에 적어 둔 비밀값을 알아서 읽어 주고, 테스트하던 계정은 이미 로그인돼 있다. 배포 환경은 이 셋을 전부 걷어낸다. 아래 7가지는 그 관대함이 사라지는 지점별로 묶었다.

에러 메시지보다 먼저 볼 것: 로그가 어디 있나

AI에게 "배포가 안 돼요"라고만 쓰면 일반론이 돌아온다. 증상마다 열어야 할 창이 다르다.

증상먼저 열 곳찾을 것
배포가 Error 상태로 끝남Vercel 프로젝트 → Deployments → 실패한 배포 → Building 로그빨간 Error 줄 중 맨 처음 것
배포는 됐는데 화면·API가 깨짐브라우저 개발자도구 Console·Network, Vercel Logs 탭실패한 요청의 상태 코드와 응답 본문
데이터가 비어 있음·권한 오류Network 탭의 Supabase 요청, Supabase 대시보드 Logs200인데 []인지, 401·403·42501인지

빌드 로그에서 하나 기억할 점. Vercel 문서는 마지막 Error 줄이 원인을 말해 주지 않는 경우가 많다고 적는다. npm run build exited with 1 같은 줄은 결과일 뿐이고, 진짜 원인은 몇 줄 위에 있다. 그래서 AI에게 넘길 때는 첫 Error 줄 위아래를 함께 자른다. 템플릿은 이렇다.

[상황] Next.js 앱을 Vercel에 배포했더니 (빌드 실패 / 배포 후 OO 화면 오류).
로컬 npm run build 결과: (성공 / 같은 오류 / 안 해 봄)
[환경] Next.js 버전, Node 버전, 내 OS(Windows/Mac), Supabase 사용 여부
[로그] 첫 번째 Error 줄 기준 위 15줄 + 아래 5줄
       (키·토큰·DB 주소는 *** 로 가림)
[해 본 것] 환경변수 확인, 재배포 등
[요청] 가능성 높은 원인 3개를 순서대로, 각각 확인 방법과 수정 diff로.

비밀값은 채팅창에 붙이지 않는다. 로그나 .env 파일을 통째로 복사하면 Supabase secret 키, DB 비밀번호가 같이 딸려 간다. 변수 이름만 남기고 값은 ***로 바꾼다. 이미 붙여 넣었다면 그 키는 교체(rotate)하는 게 안전하다.

빌드 실패 2가지: 대소문자, 그리고 타입

① Module not found — 파일명 대소문자

Vercel 빌드는 대소문자를 구분하는 리눅스 파일시스템에서 돈다. 로컬에서 파일명을 usercard.tsx → UserCard.tsx로 바꿨어도 Git은 그 변경을 놓치는 일이 잦다. 확인은 GitHub 저장소 화면에서 실제 파일명을 보는 것. 고칠 때는 Git에게 직접 시킨다.

git config core.ignorecase false
git mv components/usercard.tsx components/UserCard.tsx
git commit -m "fix: 파일명 대소문자 정정"

② 로컬 dev는 되는데 build에서 Type error

npm run dev는 타입 오류가 있어도 화면을 띄워 준다. next build는 타입 검사를 통과해야 끝난다. AI가 만든 코드에서 흔한 게 possibly 'null', 존재하지 않는 속성 접근이다. 배포 전에 로컬에서 npm run build를 한 번 돌리는 것, Vercel 문서가 가장 먼저 권하는 습관도 이것이다. AI에게는 "타입 검사를 끄지 말고 고쳐 달라"고 못 박자. ignoreBuildErrors: true로 덮는 제안이 꽤 자주 나온다.

환경변수 2가지: 넣었는데 왜 undefined?

③ Vercel에 안 넣었거나, 넣고 재배포를 안 했다

.env.local은 Git에 올라가지 않는다(올라가면 안 된다). 그러니 Vercel 프로젝트 Settings → Environment Variables에 같은 이름으로 다시 넣어야 한다. 여기서 두 번 미끄러진다.

  • 스코프: 변수마다 Production·Preview·Development를 고른다. Production만 체크하면 브랜치 미리보기 배포(Preview)에서는 값이 없다.
  • 재배포: Vercel 문서 표현 그대로, 환경변수 변경은 이전 배포에 적용되지 않고 새 배포에만 적용된다. 저장 버튼만 누르고 사이트를 새로고침하면 그대로다. Deployments에서 Redeploy를 눌러야 한다.

④ NEXT_PUBLIC_: 브라우저에서 못 읽거나, 너무 잘 읽히거나

Next.js에서 접두사 없는 변수는 서버에서만 읽힌다. 클라이언트 컴포넌트에서 process.env.SUPABASE_URL을 쓰면 undefined가 나오는 이유다. 반대로 NEXT_PUBLIC_이 붙은 값은 next build 시점에 자바스크립트 번들 안에 문자열로 박힌다. 두 가지가 따라온다.

  • 값을 바꿨다면 반드시 다시 빌드해야 한다. 빌드 후에는 변경에 반응하지 않는다(Next.js 공식 문서).
  • 번들은 누구나 열어 볼 수 있다. 비밀 키에 NEXT_PUBLIC_을 붙이는 순간 공개 정보가 된다.
// lib/supabase.ts — 브라우저용: URL과 publishable 키만
import { createClient } from '@supabase/supabase-js'

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)

Supabase 3가지: 에러가 사라지는 해결책을 조심하라

⑤ 에러는 없는데 데이터가 빈 배열 — RLS

가장 헷갈리는 증상이다. Network 탭을 보면 응답은 200, 본문은 []. Supabase 문서는 RLS를 켠 테이블은 정책을 만들기 전까지 publishable 키로는 아무 데이터도 읽을 수 없다고 설명한다. 오류가 아니라 빈 결과로 돌아온다. 로컬에서 잘 보였다면 SQL 편집기나 관리자 권한으로 확인했기 때문인 경우가 많다. 42501 오류가 뜬다면 결이 다르다. 정책 이전 단계인 테이블 권한(grant)이 빠졌다는 뜻이다.

alter table public.todos enable row level security;

create policy "본인 할 일만 조회"
on public.todos for select
to authenticated
using ( (select auth.uid()) = user_id );

여기서 AI가 자주 내놓는 지름길이 두 개 있다. "RLS를 끄세요", 그리고 "service_role 키를 쓰세요". 둘 다 에러를 정말로 없애 준다. 문제는 대가다. 2025년 보안 연구자 매트 파머가 Lovable 쇼케이스 앱 1,645개를 살펴봤더니 170개(약 10%), 엔드포인트 303곳에서 Supabase 테이블을 외부인이 읽거나 쓸 수 있었다(CVE-2025-48757). 에러가 없는 상태와 안전한 상태는 다르다.

⑥ 키를 잘못 들고 나갔다 — publishable vs secret

2026년 현재 Supabase 대시보드의 키 이름은 publishable 키(sb_publishable_...)와 secret 키(sb_secret_...)다. 예전의 anon·service_role은 각각의 레거시 버전이고, 공식 문서는 이 레거시 키를 2026년 말까지 폐기한다고 밝힌다. 아직 동작하지만 새 프로젝트라면 새 이름으로 가는 편이 낫다.

키브라우저에 둬도 되나RLS
publishable (구 anon)된다적용됨
secret (구 service_role)절대 안 됨 — 서버 코드·Vercel 환경변수(접두사 없이)에만우회함

참고로 Supabase는 브라우저에서 보낸 secret 키 요청을 401로 거절한다. 이 401을 보고 "키를 다른 데 숨기면 되나" 묻는 대신, 그 작업을 서버(Route Handler)로 옮기는 게 정답이다.

⑦ 로그인하면 localhost:3000으로 튕긴다 — 리다이렉트 URL

매직 링크나 소셜 로그인을 눌렀더니 배포 주소가 아니라 localhost:3000이 열린다. Supabase Authentication의 URL Configuration에서 Site URL을 실제 도메인으로 바꾸고, Redirect URLs에 허용 주소를 추가한다. Vercel 미리보기 배포까지 쓰려면 문서가 제시하는 와일드카드 https://*-<팀-또는-계정-슬러그>.vercel.app/**를 함께 넣는다. 코드에서는 현재 주소 기준으로 넘긴다.

await supabase.auth.signInWithOtp({
  email,
  options: { emailRedirectTo: `${window.location.origin}/auth/callback` }
})

커스텀 도메인을 연결했다면 그 도메인도 Redirect URLs에 추가했는지 다시 본다. 도메인을 바꾼 날 로그인이 깨지는 이유는 대부분 여기다.

배포 전 5분 체크리스트

  • 로컬에서 npm run build가 통과한다
  • GitHub에 올라간 파일명 대소문자가 import 경로와 같다
  • .env.local의 변수 이름이 Vercel에 전부 있고, Production·Preview 스코프를 확인했다
  • 환경변수를 바꾼 뒤 Redeploy를 눌렀다
  • NEXT_PUBLIC_이 붙은 값 중 비밀인 것이 없다
  • public 테이블마다 RLS가 켜져 있고, 필요한 정책이 있다
  • Site URL과 Redirect URLs에 실제 도메인이 들어 있다

일곱 개를 다시 보면 공통점이 하나 있다. 모두 "로컬에서는 아무도 확인하지 않던 것"이다. 대소문자, 변수 목록, 권한, 주소. 다음 배포가 실패하면 에러 메시지를 해석하기 전에 먼저 질문해 보자. 내 노트북이 이번에는 무엇을 봐주고 있었나?

참고: Vercel 환경변수 문서 · Vercel 빌드 오류 해결 문서 · Next.js 환경변수 가이드 · Supabase API 키 문서 · Supabase RLS 문서 · Supabase 리다이렉트 URL 문서 (모두 2026-11-01 확인). Lovable 사례 수치는 연구자 매트 파머 공개 자료 기준.

© 2026 주나이테크(주) @JUNAITECH