sdboard
← 목록으로

Vercel + Supabase로 풀스택 앱 만들기: 삽질 없이 설정하는 법은?

서버 없이 Next.js 풀스택 앱을 프로덕션 수준으로 운영하는 Vercel + Supabase 조합. RLS 미설정·환경 변수 누락·커넥션 풀 초과 등 실제 겪은 실수와 올바른 설정법을 단계별로 정리했습니다.

2026년 5월 18일조회 0

Vercel + Supabase 조합은 현재 인디 개발자와 스타트업 사이에서 가장 빠르게 채택되는 풀스택 인프라입니다. Next.js 배포는 Vercel, 데이터베이스·인증·스토리지는 Supabase로 나누면 별도 서버 없이 프로덕션 수준의 앱을 무료에 가깝게 운영할 수 있습니다. 단, 환경 변수 설정 오류·RLS 미설정·Auth 콜백 URL 미등록·커넥션 풀 초과, 이 네 가지 실수를 모르면 배포 직후부터 온갖 에러를 마주치게 됩니다.

왜 Vercel + Supabase인가?

서버리스 + 관리형 Postgres 조합이 강력한 이유

항목기존 방식 (EC2 + RDS)Vercel + Supabase
초기 설정 시간2~4시간15분
월 최소 비용~$30$0 (Free tier)
스케일링수동 설정자동
Auth 내장별도 구현 필요기본 제공
Realtime별도 구현 필요기본 제공

Vercel은 Next.js를 만든 회사가 운영하므로 App Router, Server Actions, Edge Runtime 지원이 가장 최적화되어 있습니다. Supabase는 PostgreSQL 위에 REST API, Realtime WebSocket, Auth, Storage를 올린 오픈소스 Firebase 대안으로, 2025년 기준 월간 활성 프로젝트가 100만 개를 돌파했습니다(출처: Supabase Blog).

핵심 이유 세 가지:

  1. 배포 파이프라인 불필요 — GitHub Push → 자동 Preview 배포 → Production 승격
  2. RLS(Row Level Security)로 API 키 없이 DB 접근 제어 — 별도 백엔드 서버 없이도 안전
  3. Edge Function + Supabase Realtime 조합으로 추가 비용 없이 실시간 기능 구현 가능

아키텍처 전체 흐름

Vercel + Supabase 요청이 브라우저에서 Edge Network, Serverless Function을 거쳐 Supabase까지 흐르는 구조

요청은 이렇게 흐릅니다:

브라우저 → Vercel Edge Network
         → Next.js Server Component (Vercel Serverless/Edge Function)
         → Supabase (Postgres + Auth + Storage + Realtime)

핵심 규칙: NEXT_PUBLIC_ 접두어가 붙은 환경 변수만 브라우저에 노출됩니다. Supabase anon key는 공개해도 되지만, service_role key는 절대 클라이언트에 노출하면 안 됩니다.

기본 설정 단계

1단계 — Supabase 프로젝트 생성 및 환경 변수 복사

Supabase 대시보드에서 새 프로젝트 생성 후 Settings → API에서 아래 두 값을 복사합니다:

NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# 서버 전용 (절대 NEXT_PUBLIC_ 붙이지 말 것)
SUPABASE_SERVICE_ROLE_KEY=eyJ...

2단계 — Next.js 프로젝트에 Supabase 클라이언트 설치

npm install @supabase/supabase-js @supabase/ssr

@supabase/ssr 패키지가 핵심입니다. 이 패키지 없이 App Router에서 세션 쿠키를 제대로 처리하지 못해 로그인 상태가 유지되지 않는 문제가 발생합니다 — 처음에 이걸 몰라서 2시간을 날렸습니다.

클라이언트 유틸리티 파일 구조:

lib/
  supabase/
    client.ts       # 브라우저용 (createBrowserClient)
    server.ts       # Server Component용 (createServerClient)
    middleware.ts   # 미들웨어용 세션 갱신
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return cookieStore.getAll() },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options))
        },
      },
    }
  )
}

3단계 — middleware.ts 반드시 설정

// middleware.ts (프로젝트 루트)
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request })
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() { return request.cookies.getAll() },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            supabaseResponse.cookies.set(name, value, options)
          })
        },
      },
    }
  )
  await supabase.auth.getUser() // 세션 자동 갱신
  return supabaseResponse
}

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

이 미들웨어를 빠뜨리면 JWT 토큰 만료 시 자동 갱신이 안 되어 사용자가 갑자기 로그아웃됩니다.

4단계 — Vercel 환경 변수 등록

로컬 .env.local에만 넣고 Vercel에 등록을 안 하면 배포 후 undefined 오류가 납니다. Vercel 대시보드 Settings → Environment Variables에서 동일한 변수들을 등록하거나 CLI로:

vercel env add NEXT_PUBLIC_SUPABASE_URL
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY
vercel env add SUPABASE_SERVICE_ROLE_KEY

실제로 겪은 삽질 4가지

Vercel + Supabase 흔한 실수 4가지

실수 1 — RLS 비활성화 상태로 배포 → 빈 배열 또는 403

Supabase에서 테이블을 만들면 기본적으로 RLS가 비활성화 상태입니다. 로컬에서는 service_role key로 테스트하면 데이터가 잘 오지만, 프로덕션에서 anon key로 접근하면 빈 배열([])이나 403이 반환됩니다.

해결: RLS를 활성화하고 정책을 명시적으로 작성하세요.

-- 테이블에 RLS 활성화
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 인증된 사용자만 자신의 데이터 조회
CREATE POLICY "users can view own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);

-- 전체 공개 읽기 허용 (블로그 글 목록 등)
CREATE POLICY "public read"
ON posts FOR SELECT
USING (true);

실수 2 — Auth 콜백 URL 미등록 → redirect_uri_mismatch

OAuth(Google, GitHub 로그인)를 붙이면 로컬에서는 되는데 배포 후 로그인이 실패합니다. Supabase 대시보드 Authentication → URL Configuration에서:

Site URL: https://your-app.vercel.app
Redirect URLs에 추가:
  https://your-app.vercel.app/auth/callback
  http://localhost:3000/auth/callback

Vercel Preview URL(https://your-app-git-branch.vercel.app)도 사용한다면 와일드카드 패턴 https://your-app-*.vercel.app/**을 추가해야 합니다.

실수 3 — Prisma + 직접 연결 시 커넥션 풀 초과

Vercel Serverless Function은 요청마다 새 인스턴스를 생성합니다. Prisma를 DATABASE_URL로 직접 연결하면 동시 요청 시 Postgres 커넥션 한도(Supabase Free: 60개)를 금방 초과합니다(출처: Supabase Connection Pooling 문서).

해결: Supabase의 Transaction Pooler(포트 6543)를 사용합니다.

# .env.local
# 직접 연결 (마이그레이션 전용)
DATABASE_URL=postgresql://postgres:[password]@db.xxx.supabase.co:5432/postgres

# Pooler 연결 (런타임용)
DATABASE_URL_POOLER=postgresql://postgres.[ref]:[password]@aws-0-ap-northeast-2.pooler.supabase.com:6543/postgres?pgbouncer=true

실수 4 — Server Component에서 쿠키 쓰기 시도 → 런타임 오류

Server Component에서 Supabase 클라이언트를 만들고 쿠키를 쓰려 하면 이런 오류가 납니다:

Error: Cookies can only be modified in a Server Action or Route Handler.

Server Component는 읽기 전용입니다. 인증 상태 변경(로그인·로그아웃)은 반드시 Server Action이나 Route Handler에서 처리해야 합니다.

// app/auth/actions.ts
'use server'
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'

export async function signOut() {
  const supabase = await createClient()
  await supabase.auth.signOut()
  redirect('/')
}

환경별 체크리스트

체크 항목로컬Vercel PreviewVercel Production
.env.local 환경 변수❌ (별도 등록)❌ (별도 등록)
Supabase Auth Redirect URLlocalhost:3000Preview URL 추가Production URL
RLS 정책 활성화확인 필요확인 필요필수
middleware.ts 존재
Pooler URL 사용선택권장필수

FAQ

Q1. Supabase Free tier로 프로덕션 서비스 운영이 가능한가요? 가능합니다. Free tier 기준으로 Postgres 500MB, 월 50,000 MAU(월간 활성 사용자), Storage 1GB를 제공합니다(Supabase 요금 페이지 참고). 다만 프로젝트 비활성 7일 시 일시 정지되므로 소규모 트래픽이 꾸준히 있는 서비스에 적합합니다.

Q2. Vercel Edge Runtime에서 Supabase를 쓸 때 주의할 점은? Edge Runtime은 Node.js API 일부를 지원하지 않아 @supabase/ssr의 쿠키 기반 세션 처리가 제한될 수 있습니다. 인증이 필요한 라우트는 export const runtime = 'nodejs'로 명시하거나 기본 Serverless Function으로 유지하는 것이 안전합니다.

Q3. Supabase Realtime을 Vercel에서 사용할 수 있나요? Realtime은 클라이언트(브라우저) 측에서 WebSocket으로 직접 Supabase에 연결하므로 Vercel 서버리스 제약과 무관합니다. supabase.channel('room').on('postgres_changes', ...).subscribe() 패턴으로 바로 사용 가능합니다.

Q4. 데이터베이스 마이그레이션은 어떻게 관리하나요? 배포 후 스키마를 변경할 때는 위 4단계에서 설정한 직접 연결 URL(DATABASE_URL)을 활용합니다. Supabase CLIsupabase migration newsupabase db push 명령으로 SQL 마이그레이션 파일을 버전 관리하고, GitHub Actions에 연동해서 자동화해두면 좋습니다.


참조

관련 글