CodeGym /행동 /ChatGPT Apps /Vercel에 배포: 리포지토리, env 변수, preview → production

Vercel에 배포: 리포지토리, env 변수, preview → production

ChatGPT Apps
레벨 7 , 레슨 3
사용 가능

1. 왜 ChatGPT App에 Vercel인가

지난 강의에서는 GiftGenius를 로컬에서 실행하고 Dev Mode와 터널을 통해 ChatGPT에 연결했습니다. 이제 “성숙한” 프로덕션에 한 걸음 더 다가가, 같은 코드를 Vercel로 옮겨 보겠습니다.

지금쯤이면 학습용 앱인 GiftGenius가 이미 동작하고 있을 것입니다. 로컬에서는 Next.js 16과 MCP 엔드포인트(예: /api/mcp)로 구동되며, 공식 ChatGPT Apps SDK Next.js Starter를 기반으로 구성되어 있습니다.

“VPS를 임대해서 Node와 nginx를 직접 설치하고 전부 수동 설정한다”는 길도 있겠지만, Next.js를 그렇게 쓰는 건 2025년에 프런트엔드를 순수 document.write로 작성하는 것과 비슷합니다. 작동은 하지만, 스스로를 불필요하게 힘들게 만드는 셈이죠.

Vercel이 우리에게 좋은 이유는 여러 가지입니다.

첫째, Vercel은 Next.js를 네이티브로 이해합니다. 빌드, SSR, 정적 자원, 엣지 레이어, 서버리스 함수까지 자동으로 설정합니다. ChatGPT App에서는 위젯과 MCP 엔드포인트를 버튼 한 번으로 같은 인프라에 배포할 수 있어 특히 편리합니다.

둘째, Vercel은 CI/CD를 기본 제공합니다. Git 리포지토리를 연결하면 각 push마다 고유 URL을 가진 새로운 불변(immutable) 배포가 생성됩니다. main 브랜치에서 나온 배포는 production으로, 그 외 브랜치에서는 preview로 간주됩니다.

셋째, Vercel은 환경과 시크릿 관리가 쾌적합니다. env 변수를 Development, Preview, Production으로 명확히 나누고 암호화해 저장하며, 이를 Next.js로 쉽게 주입할 수 있게 해줍니다. 환경에 따라 MCP 서버의 키와 URL이 달라져야 하는 ChatGPT App에 꼭 맞습니다.

넷째, Vercel에는 편리한 롤백이 있습니다. 새 릴리스가 잘못되었을 때 이전의 성공한 배포를 빠르게 프로모트하여 시스템을 안정 상태로 되돌릴 수 있습니다. 이는 “배포 공포”를 낮추고, 작고 잦은 릴리스를 장려합니다.

그리고 마지막으로, Vercel은 Next.js의 제작사입니다. 그들은 Next.js를 자사 서버에 맞추고, 자사 서버를 Next.js에 맞췄습니다. Vercel을 쓰다 보면 여러 번, 아주 부드럽게 몇 번의 클릭만으로 모든 것이 돌아가는 경험을 하게 될 겁니다. 보장합니다, 마음에 드실 거예요.

2. 출발점: GiftGenius 프로젝트 구조

우리의 GiftGenius는 하나의 리포지토리에서 관리됩니다. 구성에는 두 가지 선택지가 있으며, 둘 다 Vercel에서 문제없이 동작합니다.

1) 여러 앱이 있는 모노레포 — 예:

giftgenius/
  apps/
    web/   # Next.js (위젯 + MCP)
    mcp/   # 별도 MCP 서버(분리한 경우)

2) 단일 Next.js 프로젝트로 위젯과 MCP가 함께 존재(초기에는 이 방식이 더 단순하며, 공식 스타터도 이렇게 구성됨):

giftgenius/
  app/
    page.tsx         # 위젯
    api/
      mcp/route.ts   # MCP 엔드포인트
  next.config.mjs
  package.json
  ...

모듈 2의 강의에서 이미 Apps SDK Starter를 클론하고, 의존성을 설치한 뒤 npm run dev로 실행해 보았습니다. 지금은 다음을 가정합니다.

  • 프로젝트가 이미 Git(GitHub / GitLab / Bitbucket)에 연결되어 있고,
  • 로컬에서 .env.local에 키(OPENAI_API_KEY 등)를 보관하며,
  • ChatGPT Dev Mode가 여러분의 터널에 연결되어 있습니다.

우리의 목표는 동일한 코드가 Vercel에서도 빌드 및 실행되도록 하고, ChatGPT가 터널이 아닌 https://giftgenius.vercel.app 같은 안정적인 HTTPS 도메인으로 통신하도록 만드는 것입니다.

3. 배포를 위한 리포지토리 준비

Vercel에서 “New Project” 버튼을 누르기 전에 리포지토리를 조금 정리하는 게 좋습니다. 간단한 단계들이지만, 나중에 많은 시간을 절약해 줍니다.

첫째, .env.local.vercel이 리포지토리에 커밋되지 않도록 확인하세요. .gitignore는 Next.js Starter에 기본 포함되어 있지만, 다시 점검해 봅시다.

node_modules
.next
.env.local
.vercel

.env.local은 로컬 설정과 시크릿을 담습니다. 특히 OPENAI_API_KEY나 DB 키가 있다면 Git에 절대 올라가면 안 됩니다. Vercel에서는 UI를 통해 별도로 시크릿을 보관할 것입니다.

둘째, package.json을 확인하세요. Vercel에서는 올바른 scripts가 중요합니다.

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Vercel은 기본적으로 npm run build(또는 pnpm을 사용한다면 pnpm build)를 호출합니다. 이는 오류 없이 프로젝트를 빌드해야 합니다.

셋째, Next.js 16에 맞는 Node 버전을 지정했는지 확인하세요. Next.js 16 릴리스 노트의 최소 버전은 18.18.0입니다. 보통은 package.json에 다음 필드가 있으면 충분합니다.

{
  "engines": {
    "node": ">=18.18.0"
  }
}

Vercel은 앱과 호환되는 LTS Node 버전을 자동으로 선택합니다.

여기까지 준비됐다면 최신 코드를 Git에 푸시하고 Vercel로 넘어갑니다.

4. Vercel에 프로젝트 최초 가져오기

이제 Vercel 웹 인터페이스로 이동합니다. 아직 계정이 없다면 지금 만드세요.

Vercel에 로그인하고 “New Project”를 클릭한 뒤, 목록에서 giftgenius 리포지토리를 선택합니다. 이 단계에서 Vercel은 리포지토리 내용을 검사하고, 거의 항상 Next.js 프로젝트임을 자동으로 인식하여 해당 프리셋을 적용합니다.

프로젝트 설정에서 Vercel은 다음을 제안할 것입니다.

  • Framework = Next.js;
  • Build Command = npm run build(또는 pnpm build/yarn build);
  • Output Directory — 기본 .next(변경 불필요).

첫 배포에서는 env 변수를 바로 지정하지 않아도 됩니다(별도의 단계에서 추가). “Deploy”를 누르면 Vercel이 리포지토리를 클론하고, 의존성을 설치하고, npm run build를 실행한 뒤, 성공하면 https://giftgenius-xyz.vercel.app 같은 주소로 첫 배포를 만듭니다.

중요한 점: 각 배포는 불변(immutable)입니다. 이후 변경사항을 푸시하면 새로운 URL을 가진 새 배포가 생성되며, 기존 배포는 기록에 남습니다. 프로덕션 도메인(예: giftgenius.vercel.app 또는 여러분의 커스텀 도메인)은 특정 배포를 가리키며, 이를 이전 배포로 전환하면 롤백이 됩니다.

개념적으로는 다음과 같습니다.

flowchart LR
    A[GitHub 리포지토리
giftgenius] -->|git push| B[Vercel 빌드] B --> C[프리뷰 배포 #1
고유 URL] B --> D[프리뷰 배포 #2
고유 URL] D --> E[프로덕션 별칭
giftgenius.vercel.app]

main Git 브랜치는 보통 production 브랜치로 간주되고, 나머지는 preview입니다. 물론 이 동작은 재설정할 수 있습니다.

5. Vercel의 환경 변수

현재 첫 배포는 아마 제대로 동작하지 않을 것입니다. OPENAI_API_KEY가 없고, MCP 서버가 외부 API에 접근하지 못하는 등 문제가 있을 수 있죠. 이제 env 변수를 설정할 차례입니다.

Vercel의 env 변수는 Settings → Environment Variables에서 관리합니다. 여기서 Development, Preview, Production의 세 개 scope로 나뉘는 것을 볼 수 있습니다.

멘탈 모델을 위한 표는 다음과 같습니다.

Scope 사용 위치 로컬에서의 대응
Development vercel dev 및 Vercel CLI를 통한 로컬 dev .env.local
Preview production 브랜치를 제외한 모든 브랜치의 배포 staging / test
Production production 브랜치(보통 main)에서의 배포 “운영용” .env.prod

로컬의 .env.local과 다른 점은, Vercel이 값을 암호화해 보관하고 Next.js 코드에 process.env.MY_VAR 형태로 자동 주입한다는 것입니다.

NEXT_PUBLIC_ 프리픽스의 의미를 꼭 이해하세요. NEXT_PUBLIC_으로 시작하는 것은 브라우저 번들에 포함되어 누구나(DevTools로) 볼 수 있습니다. 공개 설정(NEXT_PUBLIC_ENV=preview, NEXT_PUBLIC_API_BASE_URL=https://giftgenius.vercel.app)에는 적합하지만, OPENAI_API_KEY 같은 키를 여기에 두는 것은 절대 금물입니다.

시크릿은 NEXT_PUBLIC_ 없이 이름을 만들고, 서버 사이드에서만 읽어야 합니다. 즉, route handler, MCP 도구 등에서만 사용하세요.

6. GiftGenius를 위한 env 설정: 예시

학습용 GiftGenius에 필요한 env 변수를 살펴봅시다.

최소한 다음이 필요할 수 있습니다.

  • OPENAI_API_KEY — 모델 호출 / MCP 클라이언트용 키
  • APP_BASE_URL — 앱의 기본 URL(https://giftgenius.vercel.app 또는 preview URL)
  • GIFTDATA_API_URL 또는 PRODUCTS_API_URL — 외부 카탈로그가 있다면

로컬 개발에서는 이것이 .env.local에 있습니다.

OPENAI_API_KEY=sk-local-...
APP_BASE_URL=http://localhost:3000
PRODUCTS_API_URL=https://dev-api.gifts.example.com

Vercel에서는 Settings → Environment Variables로 이동해 동일한 키와 값을 각 scope에 맞게 추가합니다.

MCP 엔드포인트 코드에서의 예시는 다음과 같습니다.

// app/api/mcp/route.ts
import { NextRequest } from 'next/server';

const apiKey = process.env.OPENAI_API_KEY!; // 실서비스에서는 이렇게 체크 없이 하면 안 됩니다 :)

export async function POST(req: NextRequest) {
  if (!apiKey) {
    return new Response('Missing OPENAI_API_KEY', { status: 500 });
  }
  // OpenAI 또는 다른 서비스 호출(apiKey 사용)...
}

위젯은 서버 사이드에서 APP_BASE_URL을 사용해, ChatGPT iframe과 스타터의 assetPrefix/basePath 설정을 고려한 절대 URL을 만들 수 있습니다.

클라이언트에서 공개 API URL이 필요하다면(예: window.fetch로 백엔드 호출), NEXT_PUBLIC_API_BASE_URL을 사용할 수 있습니다. 하지만 NEXT_PUBLIC_OPENAI_API_KEY 같은 것은 절대 불가입니다.

7. 프리뷰 배포: 스테이징을 강력하게

이제 가장 즐거운 부분인 프리뷰 배포입니다. Git 리포지토리를 연결하면, production 브랜치가 아닌 브랜치에 대한 push나 Pull Request마다 Vercel이 자동으로 프리뷰 배포를 만듭니다. 각 배포에는 다음과 같은 고유 URL이 부여됩니다.

https://giftgenius-git-feature-new-layout-username.vercel.app

이 배포들은 env 변수의 Preview scope를 사용하므로, 예를 들어 다음처럼 지정할 수 있습니다.

# Vercel의 Preview env
APP_BASE_URL=https://giftgenius-staging.vercel.app
PRODUCTS_API_URL=https://staging-api.gifts.example.com

그리고 이를 production과 혼동하지 않게 할 수 있습니다.

ChatGPT Dev Mode 관점에서 프리뷰 URL은 스테이징에 이상적입니다. Dev-App 설정에서 터널 URL 대신 프리뷰 URL로 엔드포인트를 바꿔, 아직 production이 아닌 빌드된 버전의 GiftGenius 동작을 확인할 수 있습니다.

자주 쓰는 방법: 기능 개발을 위해 feature/smart-recommendations 브랜치를 만들고 변경사항을 푸시하면 Vercel이 프리뷰 링크를 제공합니다. Dev Mode에서 이 링크로 URL을 바꾼 뒤, GPT와의 시나리오(선물 추천, 카드 표시, MCP 도구 호출)를 점검합니다. 모든 것이 괜찮다면 main에 머지합니다. 그동안 production은 안정적으로 별개로 운영됩니다.

파이프라인의 멘탈 모델은 다음과 같습니다.

flowchart TD
    A[로컬 개발
localhost + 터널] --> B[git push
feature/*] B --> C[프리뷰 배포
preview URL] C --> D[ChatGPT Dev Mode
앱 → preview URL] C --> E[코드 리뷰 / 테스트] E --> F[main으로 머지] F --> G[프로덕션 배포
prod URL] G --> H[ChatGPT Prod App
앱 → prod URL]

8. 프로덕션 배포와 롤백

main(또는 여러분이 지정한 production 브랜치)에 변경사항을 머지하면, Vercel은 프로덕션 배포를 만들고 프로덕션 별칭(giftgenius.vercel.app 또는 여러분의 커스텀 도메인)을 여기에 연결합니다.

이 시점에서 ChatGPT의 Prod-App(조금 뒤에 만들 것입니다)은 프로덕션 URL을 엔드포인트로 사용하도록 설정해야 합니다. Dev Mode에서는 계속 터널이나 프리뷰 URL로 실험할 수 있고, ChatGPT Store의 일반 사용자 트래픽은 프로덕션으로 향합니다.

불변 배포의 장점은 롤백이 매우 간단하다는 점입니다. 새 릴리스가 실패했다면(예: MCP 도구가 실데이터에서 오류), 운영 중인 환경에서 즉시 수정할 필요가 없습니다. Vercel의 배포 목록에서 이전에 성공한 배포를 선택하고 “Promote to Production” 같은 버튼을 누르면, 어딘가에서 K8s와 Lambda가 전환되고 여러분의 도메인은 다시 안정적인 버전을 가리키게 됩니다.

CLI에서도 vercel rollback 같은 명령으로 자동화할 수 있지만, 본 강의 수준에서는 개념만 이해해도 충분합니다. 즉, 각 배포는 별개 아티팩트이며, 프로덕션 별칭은 그중 어느 것으로든 돌릴 수 있습니다.

9. Vercel에서의 Next.js 16 + MCP 특성

Vercel의 관점에서 Next.js의 MCP 엔드포인트는 서버리스 함수(또는 그렇게 설정했다면 엣지 함수)입니다. 이 함수는 요청 시 깨어나 처리하고 종료합니다. 외부 DB나 스토리지를 사용하지 않는 이상 호출 간 상태를 보존할 수 없습니다.

이는 MCP에 매우 중요합니다. 대화 기록을 let history = [] 같은 전역 배열에 route.ts 안에 저장하려고 하면, 콜드 스타트마다 초기화됩니다. 상태 저장이 필요하다면 외부 시스템(KV, Postgres 등)이 필요하며, 이는 이후 모듈에서 다룹니다.

두 번째로 실행 시간 제한이 있습니다. 무료 플랜의 Vercel 서버리스 함수는 시간 제한이 있습니다(이 자료 준비 시점 기준으로 Hobby에서 대략 10초, Pro에서 더 김). LLM 요청, 특히 MCP 도구 체인에는 부족할 수 있습니다.

Next.js 16에서는 route handler에 대해 maxDuration을 설정해, Vercel에 더 긴 시간을 요청(플랜 한도 내)할 수 있습니다.

// app/api/mcp/route.ts
export const maxDuration = 60; // 초, Pro에서는 최대 300

export async function POST(req: Request) {
  // 오래 걸리는 작업: OpenAI 요청, 외부 DB 등
}

이건 “무제한으로 돌려줘” 버튼이 아니라, “이 함수는 오래 걸릴 수 있으니 너무 일찍 종료하지 말아줘”라고 Vercel에 알리는 올바른 방법입니다.

마지막으로 ChatGPT iframe의 특성을 잊지 마세요. Apps SDK Starter에는 이미 assetPrefixbasePath가 설정되어 있어, web-sandbox.oaiusercontent.com의 중첩 iframe 내에서도 정적 자원과 라우트가 제대로 동작합니다. 덕분에 모든 요청이 샌드박스가 아닌 여러분의 도메인으로 가며, Vercel 배포 시에도 이 설정이 유지되어 위젯이 바로 정상 동작합니다.

10. 배포 후 ChatGPT와의 통합

형식적으로는 Store와 프로덕션 모듈에 가까운 내용이지만, 배포 후 ChatGPT와의 통합은 단순하며 지금 다뤄도 무리가 없습니다.

먼저 GiftGenius를 Vercel에 배포해 프로덕션 URL을 얻습니다. 그다음 ChatGPT의 Dev Mode에서 별도의 앱(예: GiftGenius Prod)을 만들고, 설정에서 엔드포인트로 이 URL(정확히는 OpenAI Apps SDK Deploy 가이드에 맞춰 https://giftgenius.vercel.app/api/mcp 같은 MCP 엔드포인트)을 입력합니다.

개발용으로는 터널이나 프리뷰 URL을 바라보는 Dev App을 계속 사용하세요. 일일/주간 빌드를 테스트하려면 Staging-App을 만들고, 고정된 프리뷰 별칭에 연결할 수 있습니다. 결과적으로 다음과 같은 3단 구성이 됩니다.

Dev App     → 로컬 터널 또는 dev URL(불안정)
Staging App → Vercel의 안정적인 preview/staging URL
Prod App    → Vercel의 production URL

참고로 모두를 한 표로 정리하면 다음과 같습니다.

항목 URL / Vercel 배포 Vercel Scope 누가 사용
Dev App 로컬 터널 / vercel dev Development 본인 / 팀
Staging App 안정적인 preview 별칭 Preview 팀 / QA
Prod App giftgenius.vercel.app / 커스텀 도메인 Production 사용자

이는 모듈 초반에 언급했던 local / staging / prod 모델과 동일하며, 여기에 Vercel과 ChatGPT Apps를 결합한 형태입니다. 더 이상 영원한 localhost가 아닌, 성숙한 프로젝트의 아키텍처입니다.

11. Vercel 배포 시 흔한 실수

오류 №1: 시크릿이 .env.local에만 있고 Vercel에 없음.
아주 흔한 시나리오입니다. 로컬에서는 잘 동작하고 “Deploy”를 자신 있게 눌렀는데, 운영의 MCP 도구가 500과 함께 “Missing OPENAI_API_KEY”를 반환합니다. 원인은 간단합니다. Vercel은 여러분의 로컬 .env.local을 모릅니다. 동일한 변수를 Vercel 프로젝트 설정의 적절한 scope(Preview, Production)에 별도로 추가해야 합니다.

오류 №2: 민감한 데이터에 NEXT_PUBLIC_ 사용.
“일단 되게 하자”는 마음으로, 클라이언트 코드에서 키에 접근하려고 NEXT_PUBLIC_OPENAI_API_KEY를 쓰고 싶은 유혹이 생깁니다. 그 결과 키는 JS 번들에 포함되어 누구나 볼 수 있게 됩니다. 이는 단순히 나쁜 관행이 아니라, 유출과 키 차단으로 직행하는 길입니다. 모든 시크릿은 프리픽스 없이, 서버 사이드에서만 사용해야 합니다.

오류 №3: 로컬과 Vercel 환경이 불일치.
로컬에서는 제품 목록 URL이 http://localhost:4000, Vercel에서는 https://api.gifts-staging.com, 운영에서는 또 다른 URL일 수 있습니다. 필요한 env 변수를 꼼꼼히 관리하지 않고 Preview/Production에 올바르게 채워져 있는지 확인하지 않으면, 운영 위젯이 staging 백엔드로 가고, staging 위젯이 운영으로 가는 혼선이 생기기 쉽습니다. 해결책은 단순한 규율입니다. 필요한 변수를 문서화하고, 각 환경에서 값을 확인하세요.

오류 №4: MCP 엔드포인트의 실행 시간 제한을 무시.
로컬에서는 느린 외부 시스템의 응답을 30초 기다려도 문제를 느끼지 못할 수 있습니다. 하지만 Vercel에서는 같은 함수가 10–15초 후 타임아웃될 수 있고, ChatGPT는 오류를 보게 됩니다. maxDuration을 설정하지 않고 MCP 도구 실행 시간을 모니터링하지 않으면, 운영에서 간헐적인 장애로 이어질 수 있습니다.

오류 №5: 서버리스 함수 메모리에 MCP 상태를 저장하려 시도.
대화 기록이나 추천 캐시를 route handler 파일의 전역 변수 let cache = {}에 넣고 싶을 수 있습니다. 로컬에서 dev 서버가 오래 살아 있을 때는 “되는 것처럼” 보일 수 있습니다. 하지만 Vercel의 서버리스 함수는 수명이 짧고 자주 재시작됩니다. 그 결과 일부 요청은 오래된 cache, 일부는 새로운 cache, 일부는 빈 cache를 보게 됩니다. 재현하기 어려운 이상한 버그가 생깁니다. 상태 저장에는 외부 DB나 KV 스토리지가 필요합니다. 본 강의 수준에서는 MCP 엔드포인트를 무상태(stateless)로 간주하는 편이 좋습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION