이 글은 누구를 위한 것인가
- 글로벌 서비스에서 응답 속도 문제를 겪는 프론트엔드 개발자
- A/B 테스트나 지역별 콘텐츠를 빠르게 서빙하고 싶은 팀
- Vercel Edge Functions, Cloudflare Workers가 무엇인지 이해하고 싶은 분
들어가며
"서울에서는 빠른데 미국 사용자가 느리다고 해요."
이 문제의 근본 원인은 서버 위치다. 서버가 서울에 있으면 서울 사용자는 빠르지만, 미국 사용자는 데이터가 태평양을 왕복하는 시간(약 150~200ms)이 더해진다.
기존 CDN은 정적 파일(HTML, CSS, 이미지)만 전 세계에 분산 저장하고 빠르게 제공했다. 동적 콘텐츠(사용자별 개인화, A/B 테스트, 인증 검사)는 여전히 오리진 서버를 거쳐야 했다.
엣지 컴퓨팅은 이 한계를 넘는다. CDN 서버에서 단순 파일 제공을 넘어, 코드 실행이 가능해진다. 사용자와 가장 가까운 서버(엣지 노드)에서 개인화 로직이 실행되고, 결과가 즉시 응답된다.
1. 엣지 컴퓨팅이란 (CDN과의 차이)
[기존 CDN]
사용자 ──▶ CDN (정적 파일 캐시) ──▶ 오리진 서버 (동적 처리)
(멀면 150~300ms 추가)
[엣지 컴퓨팅]
사용자 ──▶ 엣지 노드 (코드 실행 가능) ──▶ 오리진 서버 (필요 시만)
└─ 50ms 이내 응답 가능
Vercel은 전 세계 70+ 엣지 위치, Cloudflare는 300+ 엣지 위치에 노드를 운영한다. 한국 사용자는 서울 또는 일본 엣지 노드가 처리한다.
엣지에서 실행 가능한 것들:
- 인증 쿠키 검증 → 로그인 여부에 따라 리다이렉트
- 지역 감지 → 국가별 다른 언어 페이지로 리다이렉트
- A/B 테스트 → 어떤 변형을 보여줄지 결정
- 요청 변환 → 헤더 추가/수정, URL 재작성
- 사용자 세그멘트 분류 → 개인화된 응답 선택
2. Next.js Middleware로 엣지 개인화 구현
Next.js의 middleware.ts는 기본적으로 엣지에서 실행된다. 모든 요청이 오리진 서버에 도달하기 전에 Middleware를 거친다.
기본 Middleware 구조
// middleware.ts (프로젝트 루트에 위치)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// 요청에서 정보 추출
const country = request.geo?.country || 'KR';
const userAgent = request.headers.get('user-agent') || '';
// 응답 헤더에 정보 추가 (페이지에서 사용 가능)
response.headers.set('x-user-country', country);
return response;
}
// Middleware 적용 경로 설정
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
지역 기반 언어 리다이렉트
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const country = request.geo?.country;
// 루트 경로에서만 적용
if (pathname === '/') {
// 일본에서 접속하면 일본어 페이지로
if (country === 'JP') {
return NextResponse.redirect(new URL('/ja', request.url));
}
// 미국에서 접속하면 영어 페이지로
if (country === 'US') {
return NextResponse.redirect(new URL('/en', request.url));
}
// 기본은 한국어 유지
}
return NextResponse.next();
}
A/B 테스트 구현
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// 기존 쿠키 확인 (재방문 사용자는 같은 변형 유지)
const variant = request.cookies.get('ab-variant')?.value;
if (!variant) {
// 새 사용자: 무작위로 변형 배정
const newVariant = Math.random() < 0.5 ? 'A' : 'B';
response.cookies.set('ab-variant', newVariant, {
maxAge: 60 * 60 * 24 * 30, // 30일
httpOnly: true,
});
response.headers.set('x-ab-variant', newVariant);
} else {
response.headers.set('x-ab-variant', variant);
}
return response;
}
페이지에서 헤더를 읽어 어떤 변형인지 알 수 있다:
// app/page.tsx
import { headers } from 'next/headers';
export default async function Page() {
const headersList = await headers();
const variant = headersList.get('x-ab-variant');
return variant === 'A' ? <VariantA /> : <VariantB />;
}
3. Cloudflare Workers로 더 강력한 엣지 로직
Cloudflare Workers는 Next.js에 종속되지 않는 범용 엣지 플랫폼이다. Node.js 환경이 아닌 V8 엔진 기반의 자체 런타임을 사용한다.
Workers + KV Storage로 개인화
Cloudflare KV는 엣지 노드에 분산 저장되는 키-값 스토리지다. 사용자 세그먼트 정보를 저장하고 조회하는 데 적합하다.
// Cloudflare Worker 예시
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = getCookieValue(request, 'user-id');
let userSegment = 'anonymous';
if (userId) {
// KV에서 사용자 세그먼트 조회 (전 세계 엣지에서 빠르게)
userSegment = await env.USER_SEGMENTS.get(userId) || 'new-user';
}
// 세그먼트에 따라 다른 콘텐츠 경로로 라우팅
if (userSegment === 'vip') {
url.pathname = `/vip${url.pathname}`;
}
// 오리진 요청에 사용자 정보 헤더 추가
const modifiedRequest = new Request(url.toString(), {
...request,
headers: {
...request.headers,
'x-user-segment': userSegment,
},
});
return fetch(modifiedRequest);
},
};
4. 엣지 캐시 전략: ISR과 엣지의 조합
정적 생성(SSG)과 엣지 캐시를 결합하면 최고의 성능을 낼 수 있다.
// Next.js의 ISR (Incremental Static Regeneration) + 엣지 캐시
async function ProductPage({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: {
revalidate: 3600, // 1시간마다 백그라운드 재생성
tags: [`product-${params.id}`], // 태그 기반 무효화
},
}).then(r => r.json());
return <ProductDetail product={product} />;
}
// 특정 제품 정보가 바뀌면 해당 캐시만 무효화
import { revalidateTag } from 'next/cache';
// API 라우트나 Server Action에서
await revalidateTag(`product-${productId}`);
캐시 계층 구조
사용자 요청
→ Cloudflare 캐시 (가장 가까운 엣지, 수ms)
→ Vercel 엣지 캐시 (ISR 캐시, ~50ms)
→ Next.js 서버 (캐시 미스 시, 100~500ms)
→ 데이터베이스/API
첫 요청은 오리진까지 가지만, 이후 요청은 엣지 캐시에서 처리된다.
5. 엣지 환경의 제약사항
엣지 런타임은 Node.js가 아니다. 사용할 수 없는 API들이 있다.
| 사용 불가 | 대안 |
|---|---|
fs (파일시스템) | 정적 import 또는 KV Storage |
child_process | 없음 (엣지 특성상 불가) |
| 대부분의 Node.js 내장 모듈 | Web API 사용 |
| 긴 실행 시간 (>30초) | 복잡한 처리는 오리진 서버로 |
| 대용량 메모리 (~128MB 제한) | 데이터 최소화 |
Next.js에서 특정 파일을 엣지 런타임으로 명시할 수 있다:
export const runtime = 'edge'; // 이 파일은 엣지에서 실행
// 또는
export const runtime = 'nodejs'; // Node.js에서 실행 (기본값)
6. 비용 분석: Vercel vs Cloudflare Workers
| 항목 | Vercel Edge Functions | Cloudflare Workers |
|---|---|---|
| 무료 티어 | 100만 요청/월 | 10만 요청/일 |
| 유료 | $0.60/100만 요청 | $0.50/100만 요청 |
| KV Storage | Next.js Cache (포함) | 별도 유료 |
| 글로벌 위치 | 70+ | 300+ |
| 최대 실행 시간 | 30초 | 30초 (무료) / 무제한 (유료) |
소규모 서비스라면 Vercel 무료 티어로 충분하다. 대규모 글로벌 서비스라면 Cloudflare의 더 많은 엣지 위치와 저렴한 단가가 유리하다.
맺으며
엣지 컴퓨팅은 "전 세계 어디서나 빠른 서비스"를 가능하게 하는 핵심 기술이다. Next.js Middleware 하나로 지역별 리다이렉트, A/B 테스트, 인증 검사를 엣지에서 처리할 수 있다.
가장 쉬운 시작점은 Next.js의 middleware.ts다. 오늘 당장 지역 감지 또는 간단한 A/B 테스트를 Middleware로 구현해보자. 오리진 서버에서 처리하던 로직이 엣지로 이동하면서 응답 속도가 눈에 띄게 개선될 것이다.