본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 07. 20:26

복잡한 Cursor 기반 앱 배포하기: Vercel, Railway, Jetpacked 환경에서의 Prisma, Postgres

요약

Cursor와 같은 AI 코딩 도구로 구축한 복잡한 풀스택 앱을 Vercel, Railway 등 다양한 플랫폼에 배포할 때 발생하는 실질적인 기술적 도전 과제를 다룹니다. 단순한 데모를 넘어 인증, DB, 백그라운드 작업, 환경 변수 설정 등 프로덕션 환경에서 직면하는 복잡성을 분석합니다.

핵심 포인트

  • AI 생성 코드는 로컬 작동과 프로덕션 배포 간의 격차가 큼
  • 빌드 타임과 런타임 환경 변수 설정의 중요성
  • Prisma, Supabase, Stripe 등 외부 서비스 통합 시의 설정 복잡성
  • 복잡한 에이전트 기반 앱의 실제 배포 검증 필요성

저는 서로 다른 배포 플랫폼들이 실제적인 AI 생성 코드를 어떻게 처리하는지 확인하기 위해 Cursor를 사용하여 복잡한 풀스택 애플리케이션을 구축했습니다.

단순한 할 일 목록(todo app) 앱이 아닙니다. 랜딩 페이지도 아닙니다. 하나의 프레임워크와 하나의 환경 변수만 사용하는 깔끔한 데모도 아닙니다. 저는 사람들이 Cursor, Claude, ChatGPT 또는 기타 최신 에이전트 기반 코딩 도구(agentic coding tools)를 사용하여 몇 번의 저녁 시간을 보낼 때 실제로 구축하게 되는 결과물에 더 가까운 것을 원했습니다. 즉, 인증(auth), 데이터베이스, 백그라운드 작업, 서드파티 API를 갖추고, 배포가 단순한 npm run build로 끝나지 않을 만큼 충분한 구성 요소들이 움직이는 제품 형태의 앱을 원했습니다.

그 결과물은 AI 영수증 스캔 기능이 있는 그룹 비용 분할 앱인 VibeSplit이었습니다.

테스트 앱

VibeSplit은 그룹이 영수증을 업로드하면 AI로 이를 파싱하고, 비용을 분할하며, 잔액을 추적하고, 다른 사람을 초대하며, 정산할 수 있게 해줍니다. 이 앱은 AI 코딩 어시스턴트를 사용하면 만들기 쉬워 보이지만, 실제로 어딘가에서 실행되어야 할 때 갑자기 훨씬 더 어려워지는 유형의 앱을 의도적으로 선택했습니다.

기술 스택은 다음과 같습니다:

  • Next.js 15 (App Router 및 TypeScript 포함)
  • Prisma 및 PostgreSQL
  • 매직 링크(magic links) 및 Google 로그인을 위한 Supabase Auth
  • 백그라운드 작업(background jobs)을 위한 Inngest
  • 영수증 파싱을 위한 OpenAI Vision
  • 정산 결제를 위한 Stripe
  • 애플리케이션 이메일을 위한 Resend

이것은 제가 보는 많은 '바이브 코딩(vibe-coded)' 앱들의 전형적인 형태입니다. 코드는 로컬에서 잘 작동하지만, 프로덕션(production) 환경에서는 실제 데이터베이스, 실제 비밀 키(secrets), OAuth 리다이렉트 URL, 웹훅(webhook) URL, 백그라운드 작업, 마이그레이션(migrations), 그리고 빌드 타임의 공개 환경 변수(public environment variables)가 필요합니다.

마지막 문구는 들리는 것보다 더 중요합니다. 어떤 변수들은 런타임(runtime) 전용입니다. NEXT_PUBLIC_SUPABASE_URL과 같은 다른 변수들은 Next.js 빌드 중에 브라우저 번들(browser bundle)에 포함됩니다. 만약 플랫폼이 컨테이너가 시작될 때만 환경 변수를 전달한다면, 백엔드는 괜찮을지 몰라도 프론트엔드는 자리 표시자(placeholder) 값과 함께 조용히 배포될 수 있습니다.

이것이 유용한 테스트였던 이유

단순한 앱은 배포 문제를 숨깁니다. 정적인 프론트엔드는 거의 모든 플랫폼을 좋아 보이게 만들 수 있습니다. 데이터베이스나 통합(integration) 기능이 없는 기본적인 Next.js 앱은 보통 큰 문제 없이 배포됩니다.

VibeSplit은 더 복잡한 부분들을 드러냈습니다:

  • Prisma는 유효한 DATABASE_URL과 프로덕션 마이그레이션 (production migrations)이 필요합니다.
  • Supabase Auth는 빌드 시점에 공개 클라이언트 값(public client values)과 배포 후의 리다이렉트 URL (redirect URLs)이 필요합니다.
  • Stripe는 라이브 앱을 가리키는 콜백 URL (callback URLs)이 필요합니다.
  • Inngest는 백그라운드 작업 (background jobs)을 위한 도달 가능한 엔드포인트 (endpoint)가 필요합니다.
  • OpenAI와 Resend는 서버 측 비밀값 (server-side secrets)이 필요합니다.
  • 앱은 컨테이너 내부의 올바른 포트 (port)에 바인딩되어야 합니다.

목표는 어떤 플랫폼이 나쁘다는 것을 증명하는 것이 아니었습니다. 목표는 대시보드에서 아키텍처를 수동으로 재구성하지 않고도, 어떤 플랫폼이 앱의 형태를 제대로 이해하고 있는지를 확인하는 것이었습니다.

Railway

Railway는 처음에는 Next.js 부분을 상당히 잘 처리했습니다. 앱이 Node/Next 프로젝트임을 감지하고 예상된 방식으로 실행하려고 시도했습니다.

첫 번째 문제는 포트 (port)였습니다. 초기 배포 시 앱과 플랫폼 간의 포트 설정이 일치하지 않아 502 에러가 발생했습니다. 이 문제는 상당히 빠르게 해결되었지만, 이것이 바로 '원클릭 배포'라는 환상을 깨뜨리는 전형적인 종류의 문제입니다. 앱은 어딘가에서 살아있었지만, 트래픽이 올바르게 도달하지 못하고 있었습니다.

포트 문제 이후에 나타난 더 큰 문제는 데이터베이스였습니다. Railway는 이 리포지토리(repo)를 위해 PostgreSQL 데이터베이스를 자동으로 프로비저닝 (provision)하지 않았습니다. 앱은 Prisma를 사용하고 Prisma 스키마 (schema)를 가지고 있었으며 명백히 데이터베이스가 필요했지만, 여전히 Postgres를 수동으로 추가해야 했습니다.

그 말은 즉, DATABASE_URL을 연결하고, Prisma 마이그레이션 (migrations)을 고민하며, 런타임 환경 (runtime environment)이 앱이 예상하는 것과 일치하는지 확인해야 함을 의미했습니다. Postgres를 추가한 후에도 여전히 내부 서버 에러 (internal server errors)가 발생했습니다. 그 시점에서 배포는 "내 리포지토리를 배포하기"에서 "플랫폼 설정, 애플리케이션의 가정, 그리고 서비스 연결 (service wiring)을 동시에 디버깅하기"로 변질되었습니다.

Railway는 무엇을 해야 하는지 알고 있다면 강력하지만, 이러한 종류의 AI 생성 풀스택 (full-stack) 앱의 경우 여전히 너무 많은 수동 재구성 (manual reconstruction)을 요구했습니다.

Vercel

Vercel은 프론트엔드 (frontend)를 성공적으로 배포했습니다. 이는 놀라운 일이 아닙니다. Vercel은 Next.js에 매우 능숙하기 때문입니다.

하지만 라이브 사이트는 500 Internal Server Error를 반환했습니다. 프론트엔드 빌드 (build)가 성공했다는 것만으로는 충분하지 않았습니다. 이 앱은 단순한 프론트엔드가 아니었습니다. 데이터베이스 (database), Prisma, 마이그레이션 (migrations), Supabase 값들, API 라우트 (API routes), 서버 액션 (server actions), 그리고 여러 서드파티 (third-party) 비밀 값 (secrets)들이 필요했습니다.

Vercel의 까다로운 점은 성공적인 배포가 기만적일 정도로 완벽해 보일 수 있다는 것입니다. 빌드는 통과하고, URL은 존재하며, 기술적으로 앱은 온라인 상태입니다. 하지만 첫 번째 실제 사용자 흐름 (user flow)이 무엇이 누락되었는지를 드러냅니다.

이 경우, 플랫폼이 애플리케이션의 전체 백엔드 (backend) 형태를 추론하지 못했습니다. 저는 여전히 데이터베이스와 환경 설정 (environment setup)을 스스로 고민해야 했습니다. 깔끔한 Next.js 앱이라면 괜찮습니다. 하지만 '바이브 코딩 (vibe-coded)'된 멀티 서비스 앱의 경우, 그 격차는 눈에 띕니다.

Jetpacked

Jetpacked는 이 앱을 단순한 Next.js 프론트엔드로 취급하는 대신, 풀스택 (full-stack) 배포로 감지했습니다.

Jetpacked는 Postgres 요구 사항, Prisma 설정, 그리고 필요한 환경 변수 (environment variables)를 파악했습니다. 덕분에 배포 과정이 제가 이 범주의 플랫폼에 기대하는 방식에 더 가까워졌습니다. 즉, 리포지토리 (repo)를 읽고, 스택 (stack)을 이해한 다음, 추론할 수 없는 값들에 대해 저에게 요청하는 방식입니다.

배포는 세 가지 플랫폼 중 마찰 (friction)이 가장 적게 완료되었습니다. 먼저 데이터베이스를 수동으로 생성한 다음 그 주변에 앱을 연결 (wire)할 필요가 없었습니다. 플랫폼은 데이터베이스가 애플리케이션 형태의 일부라는 것을 이해했습니다.

그렇다고 해서 마법 같았던 것은 아닙니다. 몇 가지 사항은 여전히 수동 작업이 필요했습니다.

Supabase Auth 리다이렉트 URL (redirect URLs)은 배포 후에 Supabase 대시보드 (dashboard)에서 구성해야 했습니다. 이는 잊어버리기 쉽습니다. Supabase가 localhost:3000만을 리다이렉트 대상으로 허용한다면, 앱이 완벽하게 배포되었더라도 로그인이 실패할 수 있습니다.

또한 저는 런타임 (runtime) 환경 변수와 빌드 타임 (build-time) 환경 변수의 차이점이라는 문제에 직면했습니다. Next.js의 경우, NEXT_PUBLIC_SUPABASE_URL과 같은 공개 변수 (public variables)는 빌드 중에 사용 가능해야 합니다. 그렇지 않으면 프론트엔드 번들 (frontend bundle)에 .env.example에 있는 플레이스홀더 (placeholder) 값이 포함될 수 있습니다. 이는 컨테이너는 올바른 런타임 환경 변수를 가지고 있더라도, 브라우저는 이미 잘못된 값으로 빌드된 코드를 실행하고 있을 수 있기 때문에 발생하는 미묘한 프로덕션 (production) 문제입니다.

이것은 배포 도구가 반드시 이해해야 하는 종류의 문제입니다. 실행 중인 컨테이너에 환경 변수를 주입하는 것만으로는 충분하지 않습니다. Next.js와 같은 프레임워크의 경우, 일부 변수는 빌드 과정에도 전달되어야 합니다.

Supabase는 별도의 단계였습니다

Supabase Auth는 별도의 배포 작업 범주를 도입했습니다.

이 앱은 매직 링크 (magic links)와 Google 로그인을 위해 Supabase를 사용했습니다. 환경 변수는 앱이 올바른 Supabase 프로젝트를 가리키도록 했지만, Supabase 자체에는 여전히 프로덕션 URL 설정이 필요했습니다.

Supabase에서 Google 프로바이더 (provider)를 활성화해야 했습니다. 배포된 콜백 URL (callback URL)을 추가해야 했습니다. 사이트 URL이 더 이상 localhost를 가리키지 않도록 설정해야 했습니다. 이 중 어느 것도 애플리케이션 리포지토리 (repo)에 들어있지 않으며, 어떤 호스팅 플랫폼도 코드만으로는 이를 완전히 추론할 수 없습니다.

이는 바이브 코더 (vibe coders)들에게 중요한 교훈입니다. 앱이 외부 인증 (external auth)을 사용하는 경우, 배포는 단순히 서버에 관한 것만이 아닙니다. 새로운 프로덕션 URL을 신뢰하도록 프로바이더를 구성하는 것도 포함됩니다.

내가 배운 것

복잡하게 바이브 코딩된 앱들은 프레임워크 이름 그 이상을 이해하는 플랫폼이 필요합니다.

"Next.js"를 감지하는 것은 유용하지만, 그것만으로는 충분하지 않습니다. 실제 배포의 형태는 Next.js에 Prisma, Postgres, 마이그레이션 (migrations), Supabase, 백그라운드 작업 (background jobs), 공개 빌드 타임 환경 변수, 그리고 서버 전용 비밀값 (server-only secrets)이 결합된 형태입니다.

Railway는 어느 정도 도움을 주었지만, 수동 서비스 설정과 포트 디버깅 (port debugging)이 필요했습니다. Vercel은 프론트엔드를 처리했지만, 그 자체만으로는 풀스택 (full-stack) 요구 사항을 해결하지 못했습니다. Jetpacked는 앱을 빠르게 라이브 상태로 만드는 데 있어 눈에 띄게 더 매끄럽게 전체 설정을 처리해 주었습니다.

그 과정에서 겪은 시행착오들은 교훈적이었습니다. Supabase는 여전히 수동으로 프로바이더(provider) 및 리다이렉트(redirect) 설정을 해주어야 합니다. OAuth는 여전히 외부 대시보드(external dashboards)에 의존합니다. 빌드 타임 환경 변수(Build-time environment variables) 또한 여전히 중요합니다. 하지만 핵심적인 배포 경로(deployment path)는 제가 원하는 모습에 훨씬 가까워졌습니다. 즉, 저장소(repo)를 연결하고, 실제 스택(stack)을 감지하며, 앱에 필요한 것을 프로비저닝(provision)하고, 남은 설정들을 명확하게 보여주는 방식입니다.

이것이 바로 AI가 생성한 애플리케이션을 위해 배포 플랫폼(deployment platforms)이 넘어야 할 기준입니다. 단순히 "Next.js 앱을 빌드할 수 있는가?"가 아니라, "누군가가 실제로 구축한 복잡하고 서비스 집약적인(service-heavy) 결과물을 이해할 수 있는가?"가 핵심입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0