Vercel 청구서 $1,243을 $78로 줄인 방법 — Next.js 미들웨어 matcher, Server Component 직접 쿼리, ISR 캐싱
Next.js 15 + Vercel을 쓰는 스타트업이 런칭 3주 만에 $1,243 청구서를 받았다. Edge Function 호출이 예상의 40배로 터진 원인과, 미들웨어 matcher·Server Component 직접 쿼리·ISR 세 가지 변경으로 94% 비용을 줄인 실제 사례를 3인칭으로 정리했다.
한 줄 요약: Next.js 앱을 Vercel에 배포한 스타트업이 런칭 3주 만에 $1,243 청구서를 받았다. Edge Function 호출이 예상의 40배로 터진 것이 원인이었고, 이 팀은 미들웨어 matcher 설정, Server Component 직접 쿼리, ISR 캐싱 세 가지 변경으로 청구액을 $78까지 낮췄다.
이 글이 필요한 사람
Next.js 앱을 Vercel에 배포하고 있는 개발자 또는 팀
Vercel 청구서가 예상보다 높게 나와 원인을 찾고 있는 사람
Edge Function, ISR, 미들웨어의 비용 구조를 이해하고 싶은 개발자
※ 이 사례는 Indie Hackers 및 개발자 커뮤니티에 공개된 실제 사례를 3인칭으로 재구성했다. (2026년 4월 기준)
런칭 3주 만에 날아온 $1,243 청구서
이 개발자는 2025년 말, B2B SaaS 제품을 혼자 만들어 Vercel에 배포했다. 기술 스택은 Next.js 15 App Router + Vercel Pro + Supabase. 런칭 초기에는 Pro 플랜($20/월)으로 충분하다고 생각했다.
런칭 3주 후, Vercel 대시보드의 Usage 탭을 열었다가 눈을 의심했다. Edge Function Invocations가 4,200만 회를 기록하고 있었고, 월 청구 예상액이 $1,243으로 표시되어 있었다.
Vercel Pro 플랜에는 무료 Edge Function 호출 500만 회가 포함된다. 초과분은 100만 회당 $2가 부과된다. 이 팀은 3,700만 회를 초과했으니 Edge Function 초과분만 약 $74다. 나머지 $1,100을 넘는 금액은 Function Duration, Bandwidth, Image Optimization 항목이 합산된 결과였다.
이 개발자가 글을 올렸을 때 돌아온 첫 번째 질문은 하나였다. "미들웨어에 matcher 설정했어요?"
Vercel Usage 탭에서 Edge Function Invocations가 예상치를 초과하면 실시간으로 경고가 표시된다.
원인 분석 — 미들웨어가 모든 요청을 Edge에서 처리하고 있었다
이 개발자는 Supabase 인증 처리를 위해 middleware.ts에 세션 갱신 로직을 넣었다. Supabase 공식 Next.js 가이드에서 권장하는 방식 그대로였다. 문제는 matcher 설정이 없었다는 것이다.
Next.js에서 middleware.ts는 matcher를 설정하지 않으면 모든 경로에 대해 실행된다. _next/static, _next/image, favicon.ico, 폰트 파일, API 헬스체크까지 포함해서. 이 프로덕트는 Next.js Image Optimization을 적극적으로 쓰고 있었는데, 이미지 최적화 요청 하나가 여러 번의 Edge Function 호출을 유발하고 있었다.
문제가 된 middleware.ts — matcher 미설정
// 수정 전: 모든 경로에서 실행됨
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareClient({ req, res })
// 매 요청마다 Supabase 세션 갱신 API 호출
await supabase.auth.getSession()
return res
}
// matcher 없음 → _next/static, _next/image 등 모든 요청에 실행
Vercel Edge Runtime에서 미들웨어가 실행될 때마다 하나의 Edge Function Invocation이 과금된다. 정적 파일 요청, 이미지 최적화 요청, 심지어 robots.txt까지 모두 카운팅되고 있었다.
두 번째 문제도 있었다. 이 팀은 Server Component 안에서 내부 API Route를 직접 fetch하는 패턴을 쓰고 있었다. Pages Router에서 App Router로 이전하면서 기존 패턴을 그대로 가져온 것이다. 하나의 페이지 로드에 평균 4~6개의 API Route fetch가 발생했고, 각각이 별도의 Edge Function Invocation으로 집계됐다. DAU 300명 수준이었음에도 하루 140만 호출이 발생한 이유가 여기에 있었다.
Vercel 과금 구조 핵심 Edge Function은 호출 횟수(Invocations)와 실행 시간(Duration)으로 과금된다. 미들웨어도 Edge Function으로 취급된다. 정적 파일(public/)은 호출 카운팅에서 제외되지만, _next/image 최적화 요청은 포함된다.
첫 번째 수정 — 미들웨어 matcher로 불필요한 호출 60% 차단
커뮤니티 조언을 받은 이 개발자는 즉시 middleware.ts에 matcher 설정을 추가했다. 인증이 실제로 필요한 경로에만 미들웨어를 실행하도록 제한한 것이다.
수정된 middleware.ts — matcher 적용
// 수정 후: 인증이 필요한 경로만 실행
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareClient({ req, res })
await supabase.auth.getSession()
return res
}
export const config = {
matcher: [
// 정적 파일, 이미지, favicon 제외
'/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
// 인증이 필요한 경로만 명시
'/dashboard/:path*',
'/settings/:path*',
'/api/auth/:path*',
],
}
matcher를 적용한 것만으로 Edge Function Invocations가 약 60% 감소했다. 하루 140만 건에서 56만 건으로 줄어든 것이다. 그러나 청구 예상액은 여전히 $400을 넘었다. 두 번째 문제, Server Component의 내부 API Route fetch 패턴을 해결해야 했다.
Server Component에서 직접 DB를 쿼리하면 네트워크 홉이 제거된다. API Route를 거치면 Edge Function Invocation이 추가로 발생한다.
두 번째 수정 — Server Component 직접 쿼리와 ISR 캐싱
이 개발자는 다음 주에 API Route 의존성을 제거하는 리팩토링을 진행했다. Server Component에서 Supabase 클라이언트를 직접 호출해 DB를 쿼리하는 방식으로 전환했다. 이렇게 하면 API Route 호출이 사라지므로 그만큼의 Edge Function Invocation이 줄어든다.
Server Component 직접 쿼리로 API Route 우회
// 수정 전: Server Component에서 API Route 호출 (Invocation 발생)
export default async function DashboardPage() {
const res = await fetch('/api/dashboard/stats')
const data = await res.json()
return <Dashboard data={data} />
}
// 수정 후: Server Component에서 직접 Supabase 쿼리
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
export default async function DashboardPage() {
const supabase = createServerComponentClient({ cookies })
const { data: user } = await supabase.auth.getUser()
const { data } = await supabase
.from('stats')
.select('*')
.eq('user_id', user.user?.id)
return <Dashboard data={data} />
}
// 정적 콘텐츠 페이지는 ISR로 캐싱 (CDN에서 처리 → Invocation 없음)
// app/pricing/page.tsx
export const revalidate = 3600 // 1시간마다 재생성
세 번째로 손을 댄 곳은 공개 페이지의 렌더링 전략이었다. 마케팅 랜딩 페이지, 가격 안내 페이지, 블로그는 로그인 여부와 관계없이 동일한 내용을 보여준다. 그런데 기존 코드에는 export const dynamic = 'force-dynamic'이 붙어 있어 방문할 때마다 서버 사이드 렌더링이 발생하고 있었다.
이 개발자는 해당 페이지들에 ISR(Incremental Static Regeneration)을 적용했다. revalidate를 3600(1시간)으로 설정하면, 첫 번째 방문에서만 렌더링이 일어나고 이후 요청은 CDN 캐시에서 처리된다. Edge Function Invocation 자체가 발생하지 않는다.
ISR vs SSR 비용 차이 SSR(force-dynamic): 매 요청마다 Edge/Serverless Function 실행 → 방문자 수 × 페이지 수만큼 과금 ISR(revalidate): 최초 1회 + 재검증 주기마다 1회 → 트래픽이 늘어도 비용 거의 고정
3주 후 결과 — 월 청구액 $1,243에서 $78로
이 개발자가 커뮤니티에 올린 후속 글의 제목은 "$1,200짜리 Vercel 청구서를 $78로 줄였다"였다. 변경 사항을 배포하고 3주가 지난 시점의 결과였다.
Edge Function Invocations: 4,200만 → 380만 (91% 감소)
Function Duration: 86% 감소 (API Route 삭제로 실행 시간 단축)
Bandwidth: 12% 감소 (CDN 캐시 히트율 증가)
월 청구 예상액: $1,243 → $78
이 팀은 같은 사용자 수(DAU 300~350명), 같은 기능을 그대로 유지하면서 인프라 비용을 94% 줄이는 데 성공했다. 변경에 걸린 실제 개발 시간은 약 이틀이었다고 밝혔다.
미들웨어 matcher 설정, Server Component 직접 쿼리, ISR 세 가지 변경으로 비용을 94% 줄인 실제 결과 기록
Vercel 비용 폭탄의 4가지 공통 패턴
이 사례에서 확인할 수 있는 Vercel 청구 폭탄의 공통 패턴은 네 가지다.
1. 미들웨어 matcher 미설정 Vercel 공식 문서와 Supabase 가이드 예시 코드에 matcher 없이 미들웨어를 보여주는 경우가 있다. 실제 프로덕션에서 matcher 없이 운영하면 _next/image 등 정적 리소스 요청까지 모두 Edge에서 실행된다.
2. Server Component에서 내부 API Route를 fetch하는 패턴 Pages Router에서 App Router로 이전할 때 가장 흔히 발생하는 비효율이다. fetch('/api/...')를 Server Component 안에 남겨두면 서버→서버 네트워크 요청이 발생하고, API Route도 별도의 Function Invocation으로 카운팅된다.
3. 동적 데이터가 없는 페이지에 force-dynamic 적용 export const dynamic = 'force-dynamic'은 캐싱을 완전히 비활성화한다. 콘텐츠가 실시간으로 바뀔 필요가 없는 페이지에 이 설정이 있으면 방문할 때마다 서버 렌더링이 발생한다.
4. Vercel Spend Management 알림 미설정 Vercel은 Pro 이상 플랜에서 월 한도를 설정하는 Spend Management 기능을 제공한다. 이 팀은 청구서를 받고 나서야 이 기능이 있다는 것을 알았다고 했다.
지금 바로 설정할 것 Vercel 대시보드 → Settings → Spend Management에서 월 한도를 설정할 것. Pro 플랜은 기본적으로 청구 상한이 없으므로 무제한으로 과금될 수 있다.
Vercel이 비싸지는 조건과 대안 선택 기준
이 사례를 계기로 이 개발자는 Vercel이 비싸지는 조건과 적합한 상황을 정리했다. 그 판단 기준을 그대로 가져왔다.
상황
비용 리스크
대안
미들웨어 전역 적용 + SSR 전페이지
높음
matcher 설정 + ISR
Next.js Image Optimization 대량 사용
높음
Cloudflare Images 또는 CDN 직접 서빙
API Route를 BFF처럼 활용
중간
Server Component 직접 쿼리
트래픽 급증 예상 (마케팅 캠페인)
높음
Vercel Enterprise 고정 요금 또는 AWS ECS
정적 콘텐츠 위주 마케팅 사이트
낮음
Vercel Hobby 또는 Pro 그대로 유지
이 개발자는 결론적으로 Vercel을 유지했다. 최적화 이후 월 $78은 개발 편의성과 배포 속도를 고려하면 합리적이라고 판단했다. 다만 트래픽이 10배 이상 늘어날 시점에는 AWS ECS + CloudFront 마이그레이션을 미리 검토하겠다고 밝혔다.