본문으로 건너뛰기 Vercel 완벽 가이드 | 배포·Edge Functions·Analytics·환경 변수·실전 활용

Vercel 완벽 가이드 | 배포·Edge Functions·Analytics·환경 변수·실전 활용

Vercel 완벽 가이드 | 배포·Edge Functions·Analytics·환경 변수·실전 활용

이 글의 핵심

Vercel로 빠른 배포를 구현하는 완벽 가이드. Git 연동, Edge Functions, Analytics, 환경 변수, 도메인 설정까지 실전 예제로 정리. Vercel·Deployment·Edge Functions 중심으로 설명합니다.

Vercel이란 무엇인가

Vercel은 Next.js를 만든 회사가 직접 운영하는 프런트엔드 배포 플랫폼입니다. 예전 이름은 ZEIT이었고, 지금은 프레임워크 개발과 배포 인프라를 한 회사가 함께 가져가는 방향으로 사업을 키우고 있습니다. Git 저장소를 연결하고 브랜치에 푸시하면 자동으로 빌드와 배포가 이루어지는 구조라서, AWS에서 VPC와 로드밸런서, 오토스케일링 그룹을 하나씩 구성하는 방식보다 훨씬 진입 장벽이 낮습니다.

이런 구조가 특히 잘 맞는 팀은 Next.js나 React 기반 프로젝트를 운영하는 팀입니다. Netlify도 정적 사이트 배포에 강점이 있고 Cloudflare Pages도 엣지 네트워크 측면에서 매력적이지만, Next.js와의 통합 완성도만 놓고 보면 Vercel이 한 발 앞서 있는 경우가 많습니다. 예를 들어 Next.js의 ISR(Incremental Static Regeneration)이나 App Router의 서버 컴포넌트 스트리밍 같은 기능은 Vercel 인프라에 맞춰 최적화되어 있어서, 다른 플랫폼에서 동일한 기능을 구현하려면 추가 설정이 필요한 경우가 있습니다.

AWS에서 Vercel로 마이그레이션한 팀들이 흔히 언급하는 변화는 배포 시간 단축입니다. 수동으로 구성한 CI/CD 파이프라인에서 배포에 10분 가까이 걸리던 프로젝트가, Vercel의 캐시된 빌드 파이프라인을 거치면 30초 안팎으로 줄어드는 경우가 드물지 않습니다. 물론 이는 프로젝트 규모와 캐시 적중 여부에 따라 달라지므로, 모든 프로젝트에 동일하게 적용되는 수치는 아닙니다.

Git 연동 배포 흐름

Vercel의 핵심 배포 흐름은 다음과 같습니다.

  1. GitHub, GitLab, Bitbucket 중 하나의 저장소를 Vercel 대시보드에 연결합니다.
  2. 배포할 브랜치를 지정합니다(기본값은 보통 main).
  3. Vercel이 package.json을 분석해 프레임워크를 자동으로 감지하고, 빌드 명령과 출력 디렉터리를 추론합니다.
  4. 커밋을 푸시하면 자동으로 빌드가 실행되고, 성공하면 고유 URL이 생성됩니다.

이 흐름의 가장 큰 장점은 Pull Request마다 독립된 프리뷰 URL이 생성된다는 점입니다. 리뷰어가 코드를 읽지 않고도 실제로 동작하는 화면을 클릭해볼 수 있기 때문에, 디자인 리뷰나 QA 과정에서 커뮤니케이션 비용이 크게 줄어듭니다.

CLI로 직접 배포하고 싶다면 다음과 같이 진행합니다.

# Vercel CLI 설치
npm install -g vercel

# 로그인 (브라우저 인증)
vercel login

# 현재 디렉터리를 프로젝트로 연결하고 배포
vercel

# 프로덕션 배포 (main 브랜치가 아니어도 강제로 프로덕션에 반영)
vercel --prod

vercel 명령을 처음 실행하면 프로젝트 이름, 연결할 스코프(개인/팀), 기존 설정 사용 여부 등을 대화형으로 물어봅니다. 한 번 설정하면 .vercel/project.json에 프로젝트 정보가 저장되어, 이후에는 옵션 없이 vercel만 실행해도 동일한 프로젝트로 배포됩니다.

프리뷰 배포에만 접근 제한을 걸고 싶은 경우도 있습니다. 예를 들어 클라이언트에게 공유하기 전 내부 검토용으로만 프리뷰를 열어두고 싶을 때는, Next.js 미들웨어에서 환경을 구분해 Basic Auth를 적용할 수 있습니다.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 프리뷰 환경에서만 인증을 요구합니다
  if (process.env.VERCEL_ENV === 'preview') {
    const basicAuth = request.headers.get('authorization');
    const validAuth = `Basic ${Buffer.from(
      `${process.env.PREVIEW_USER}:${process.env.PREVIEW_PASSWORD}`
    ).toString('base64')}`;

    if (basicAuth !== validAuth) {
      return new NextResponse('Authentication required', {
        status: 401,
        headers: { 'WWW-Authenticate': 'Basic realm="Preview"' },
      });
    }
  }
  return NextResponse.next();
}

export const config = {
  matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
};

여기서 주의할 점은 사용자 이름과 비밀번호를 코드에 하드코딩하지 않고 반드시 환경 변수로 분리해야 한다는 것입니다. VERCEL_ENV는 Vercel이 배포 시점에 자동으로 주입하는 값으로, production, preview, development 중 하나를 가지므로 별도 설정 없이 환경을 구분할 수 있습니다.

환경 변수 관리와 실수 사례

환경 변수를 다룰 때 가장 흔히 저지르는 실수는 Production 환경에만 값을 등록하고 Preview 환경을 빠뜨리는 것입니다. 실제로 첫 배포 당시 DATABASE_URLAPI_KEY를 대시보드에 등록하면서 Production 탭에만 입력하고 Preview 탭은 비워둔 적이 있습니다. 그 결과 메인 프로덕션 사이트는 정상 동작했지만, PR을 열 때마다 생성되는 프리뷰 URL에서는 API 호출이 전부 500 에러로 실패했습니다. 로그를 한참 뒤지고 나서야 원인이 환경별 변수 누락이라는 것을 알았습니다.

이 경험 이후로는 팀 규칙으로 “프리뷰 환경에도 반드시 동일한 키를 등록한다(값은 별도 스테이징 리소스를 가리켜도 무방하다)“는 원칙을 세워두었습니다. Vercel 대시보드의 Environment Variables 설정에서는 각 변수마다 Production, Preview, Development 세 가지 환경 중 어디에 적용할지 체크박스로 선택할 수 있으므로, 변수를 추가할 때마다 세 환경 모두를 확인하는 습관을 들이는 것이 좋습니다.

로컬 개발 환경을 프로덕션과 동일하게 맞추고 싶다면 Vercel CLI의 env pull 명령을 사용합니다.

# 원격에 등록된 환경 변수를 로컬 .env.local로 내려받기
vercel env pull .env.local

# 특정 환경을 지정해서 받기
vercel env pull .env.local --environment=preview

이 명령을 실행하면 대시보드에 등록된 값들이 그대로 .env.local 파일에 기록됩니다. .env.local은 기본적으로 .gitignore에 포함되므로 저장소에 비밀 값이 커밋될 위험은 낮지만, 그래도 커밋 전에는 항상 git status로 확인하는 습관을 들이는 것이 안전합니다.

Next.js를 사용하는 경우 서버 사이드 코드에서는 process.env.VARIABLE_NAME 형태로 모든 환경 변수에 접근할 수 있지만, 브라우저로 노출되는 클라이언트 번들에는 NEXT_PUBLIC_ 접두사가 붙은 변수만 포함됩니다.

// 서버에서만 읽을 수 있음 (API Route, 서버 컴포넌트 등)
const dbUrl = process.env.DATABASE_URL;

// 클라이언트에서도 읽을 수 있음 (번들에 포함되어 노출됨)
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;

DB 접속 정보나 API 키처럼 유출되면 안 되는 값에는 NEXT_PUBLIC_ 접두사를 절대 붙이지 않아야 합니다. 이 접두사가 붙는 순간 빌드 타임에 클라이언트 자바스크립트 번들 안에 값이 그대로 포함되어, 누구나 브라우저 개발자 도구에서 확인할 수 있게 됩니다.

Edge Functions vs Node 서버리스 런타임

Vercel에서 서버 코드를 실행하는 방식은 크게 두 가지입니다. 하나는 Edge Runtime이고, 다른 하나는 전통적인 Node.js 기반 서버리스 함수입니다.

Edge Runtime은 V8 isolate 위에서 실행되며, 전 세계 여러 리전에 분산 배치되어 사용자와 물리적으로 가까운 곳에서 요청을 처리합니다. 그 덕분에 콜드 스타트가 거의 체감되지 않을 정도로 짧다는 장점이 있습니다. 다만 Node.js의 모든 API를 지원하지는 않습니다. 예를 들어 fs 모듈로 파일 시스템에 접근하거나, 일부 네이티브 애드온에 의존하는 패키지는 Edge Runtime에서 동작하지 않습니다.

반대로 Node 서버리스 런타임은 완전한 Node.js 환경을 제공하므로, 파일 시스템 접근이 필요하거나 무거운 라이브러리를 사용하는 경우, 혹은 실행 시간이 긴 작업에 적합합니다. 데이터베이스 커넥션 풀을 유지하거나 복잡한 이미지 처리를 수행하는 API는 보통 Node 런타임 쪽을 선택하게 됩니다.

Next.js App Router에서는 라우트 파일에 한 줄만 추가하면 Edge Runtime으로 전환할 수 있습니다.

// app/api/geo/route.ts
export const runtime = 'edge';

export async function GET(request: Request) {
  // Edge Runtime에서는 Vercel이 지역 정보를 헤더로 주입해줍니다
  const country = request.headers.get('x-vercel-ip-country') ?? 'unknown';
  const city = request.headers.get('x-vercel-ip-city') ?? 'unknown';

  return Response.json({ country, city });
}

x-vercel-ip-country, x-vercel-ip-city 같은 헤더는 Vercel의 엣지 네트워크가 요청을 처리하는 시점에 자동으로 주입해주는 값으로, 별도의 GeoIP 서비스를 연동하지 않아도 대략적인 사용자 위치 정보를 얻을 수 있습니다. 다국어 리다이렉트나 지역별 콘텐츠 분기 같은 기능을 구현할 때 유용합니다.

어떤 런타임을 선택할지 판단하는 기준을 정리하면 다음과 같습니다.

  • 지연 시간이 중요하고 가벼운 로직(인증 체크, 리다이렉트, A/B 테스트 분기)이라면 Edge
  • 데이터베이스 ORM, 이미지 처리, 파일 시스템 접근이 필요하다면 Node 서버리스
  • 실행 시간이 10초를 넘길 가능성이 있다면 Node 서버리스 (Edge는 실행 시간 제한이 더 엄격합니다)

vercel.json 설정

프로젝트 루트에 vercel.json 파일을 두면 리전, 헤더, 리다이렉트, 재작성(rewrite) 규칙 등을 코드로 명시할 수 있습니다. 대시보드에서 클릭으로 설정하는 것보다 버전 관리가 쉽고, 팀원 전체가 동일한 설정을 공유할 수 있다는 장점이 있습니다.

{
  "regions": ["icn1"],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options", "value": "DENY" }
      ]
    }
  ],
  "redirects": [
    { "source": "/old-blog/:slug", "destination": "/blog/:slug", "permanent": true }
  ],
  "rewrites": [
    { "source": "/api/proxy/:path*", "destination": "https://api.example.com/:path*" }
  ]
}

regions 필드에 icn1(서울)을 지정하면 서버리스 함수가 서울 리전에서 실행됩니다. 한국 사용자 대상 서비스라면 지연 시간을 줄이는 데 도움이 되지만, 여기서 반드시 함께 확인해야 할 것이 있습니다. 바로 함수가 접속하는 데이터베이스나 외부 API의 위치입니다. 함수는 서울에 있는데 데이터베이스가 미국 동부 리전에 있다면, 함수 자체의 지연은 줄어도 함수와 DB 사이의 왕복 시간이 그대로 남아 있어 전체 응답 시간 개선 효과가 기대만큼 크지 않을 수 있습니다. 함수 리전과 데이터 저장소 리전은 항상 함께 고려해야 합니다.

redirectsrewrites는 비슷해 보이지만 동작이 다릅니다. redirects는 브라우저에 실제로 3xx 응답을 보내 URL이 바뀌었다는 것을 알리고, rewrites는 URL은 그대로 유지한 채 서버 내부적으로 다른 경로의 콘텐츠를 보여줍니다. 레거시 URL을 새 경로로 영구 이전할 때는 redirects(SEO 상 permanent: true로 301 처리), 외부 API를 프록시해서 CORS 문제를 피하고 싶을 때는 rewrites를 사용하는 것이 일반적입니다.

ISR을 이용한 캐시 재검증

Next.js의 ISR(Incremental Static Regeneration)을 사용하면 정적 페이지처럼 빠르게 서빙하면서도, 지정한 주기마다 백그라운드에서 콘텐츠를 갱신할 수 있습니다.

// app/blog/[slug]/page.tsx
export const revalidate = 3600; // 1시간마다 재검증

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return <article>{/* ... */}</article>;
}

revalidate 값은 “이 시간이 지난 뒤 들어오는 요청부터 백그라운드에서 페이지를 다시 생성한다”는 의미입니다. 즉시 반영되는 것이 아니라, 캐시가 만료된 이후 첫 요청이 트리거가 되어 재생성이 시작되고, 재생성이 끝나기 전까지는 기존 캐시된 페이지가 계속 서빙됩니다. 이 방식 덕분에 트래픽이 몰리는 순간에도 페이지가 매번 새로 렌더링되면서 응답이 느려지는 상황을 피할 수 있습니다.

콘텐츠가 변경된 즉시 캐시를 무효화하고 싶다면 On-Demand Revalidation을 사용할 수도 있습니다.

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';

export async function POST(request: Request) {
  const { secret, path } = await request.json();

  if (secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ message: 'Invalid secret' }, { status: 401 });
  }

  revalidatePath(path);
  return Response.json({ revalidated: true, path });
}

CMS에서 콘텐츠를 발행할 때 웹훅으로 이 엔드포인트를 호출하도록 구성하면, 정해진 시간 주기와 무관하게 원하는 시점에 즉시 캐시를 갱신할 수 있습니다. secret 값을 검증하는 로직을 반드시 포함해서, 누구나 이 엔드포인트를 호출해 불필요한 재생성을 유발하지 못하도록 막아야 합니다.

Analytics와 Speed Insights

@vercel/analytics@vercel/speed-insights 패키지를 사용하면 별도의 서드파티 분석 도구 없이도 방문자 수, Core Web Vitals(LCP, FID, CLS 등) 지표를 대시보드에서 확인할 수 있습니다.

npm install @vercel/analytics @vercel/speed-insights
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}

레이아웃 컴포넌트에 두 컴포넌트를 추가하는 것만으로 계측이 시작됩니다. 별도의 스크립트 태그를 수동으로 삽입하거나 API 키를 발급받아 설정할 필요가 없다는 점이 장점입니다. 이 지표들은 “느린 것 같다”는 감각적인 판단이 아니라, 실제 방문자의 브라우저에서 수집된 실측 데이터(RUM, Real User Monitoring)를 기반으로 하기 때문에, 최적화 작업의 우선순위를 정할 때 근거로 활용하기 좋습니다.

도메인 연결

커스텀 도메인은 대시보드의 Domains 메뉴에서 추가합니다. 도메인을 입력하면 Vercel이 필요한 DNS 레코드 값을 안내해주는데, 일반적인 구성은 다음과 같습니다.

  • 루트 도메인(example.com): A 레코드를 76.76.21.21로 설정
  • www 서브도메인: CNAME 레코드를 cname.vercel-dns.com으로 설정

다만 이 값은 시점이나 계정 설정에 따라 달라질 수 있으므로, 실제 작업 시에는 반드시 대시보드에 표시되는 안내 값을 그대로 사용해야 합니다. 레지스트라(도메인 등록기관)의 DNS 관리 화면에서 위 레코드를 등록하면, 보통 몇 분에서 최대 몇 시간 내에 전파가 완료되고 Vercel이 자동으로 SSL 인증서를 발급합니다.

빌드 실패와 트러블슈팅

배포가 실패했을 때 가장 먼저 확인해야 할 것은 빌드 로그입니다. 대시보드의 Deployments 탭에서 실패한 배포를 클릭하면 전체 빌드 로그를 볼 수 있는데, 흔히 발생하는 원인은 다음과 같습니다.

  • 환경 변수 누락: 로컬에서는 동작하지만 배포 환경에만 값이 등록되지 않은 경우
  • Node.js 버전 불일치: 로컬 개발 환경과 Vercel의 기본 Node 버전이 다른 경우 (package.jsonengines 필드로 고정 가능)
  • 패키지 매니저 차이: package-lock.json, yarn.lock, pnpm-lock.yaml이 동시에 존재해 예상치 못한 패키지 매니저가 선택되는 경우
// package.json
{
  "engines": {
    "node": "20.x"
  }
}

engines 필드로 Node 버전을 명시해두면, 로컬 개발 환경과 배포 환경의 버전 차이로 인한 문제를 사전에 줄일 수 있습니다.

간헐적으로만 발생하는 오류라면 다음 항목들을 의심해볼 만합니다.

  • 외부 API 호출의 타임아웃 (Edge/Node 함수 모두 실행 시간 제한이 있습니다)
  • Preview 환경에만 환경 변수가 비어 있는 경우
  • 특정 리전에서만 재현되는 네트워크 이슈 (함수 리전과 DB 리전 간의 지연)

정리

빠르게 UI를 배포하고, Pull Request마다 독립된 프리뷰 URL을 받아 협업하고 싶다면 Vercel은 여전히 가장 마찰이 적은 선택지 중 하나입니다. 무료 Hobby 플랜만으로도 개인 프로젝트나 사이드 프로젝트를 충분히 운영할 수 있고, Next.js뿐 아니라 Vue, Svelte, Astro 등 다양한 프레임워크의 배포도 지원합니다.

AWS처럼 인프라를 세밀하게 제어할 수 있는 것은 아니지만, 배포 경험의 완성도만 놓고 보면 대체하기 쉽지 않은 수준입니다. 이미 팀에서 정해둔 인프라 스택이 있다면 그것을 따르는 것이 맞겠지만, 새로 시작하는 프로젝트라면 우선 Vercel로 배포해보고, 실제로 한계를 느꼈을 때 다른 플랫폼으로 옮기는 순서를 권합니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.