🏢 Vercel 기술 스택 분석
버셀이 프론트엔드 배포의 미래를 만드는 기술 스택 심층 분석 - Next.js, Edge Functions, 그리고 글로벌 엣지 네트워크
🏢 Vercel 기술 스택 분석
프론트엔드 개발자를 위한 최고의 배포 플랫폼을 만드는 Vercel의 기술 스택을 심층 분석합니다.
"Develop. Preview. Ship." - 프론트엔드 배포를 혁신하는 기술
📊 회사 개요
서비스 규모
- 월간 배포: 1,000만+ 건
- 개발자: 100만+ 명
- 월간 요청: 300억+ 건
- 엣지 로케이션: 전 세계 100+ 개
- 평균 빌드 시간: < 1분
엔지니어링 조직
- 엔지니어: 200명+ (전체 직원 350+)
- 문화: "Ship Early, Ship Often"
- 원격 우선: 글로벌 분산 팀
- 오픈소스: Next.js, Turborepo 등 주도
기술적 도전과제
- 즉각적 배포: Git push에서 프로덕션까지
- 글로벌 성능: 전 세계 밀리초 응답
- 개발자 경험: 완벽한 로컬-프로덕션 패리티
- 엣지 컴퓨팅: 서버리스의 다음 단계
🏗️ 아키텍처 Overview
시스템 다이어그램
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Git Push │────►│ Builder │────►│ Registry │
│ (GitHub) │ │ Platform │ │ (OCI) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐
│ │ Preview │ │Edge Network│
│ │ URLs │ │(100+ PoPs) │
│ └───────────┘ └───────────┘
│ │ │
└────────────────────┤ │
┌─────▼─────┐ ┌─────▼─────┐
│ Analytics │ │ Edge │
│ Engine │ │ Functions │
└───────────┘ └───────────┘
핵심 설계 원칙
- Zero Config: 설정 없이 바로 배포
- Edge First: 사용자 가까이에서 실행
- Instant Rollbacks: 즉시 이전 버전으로
- Preview Deployments: 모든 PR에 미리보기
- Framework Agnostic: 모든 프레임워크 지원
기술 진화
- 2015: ZEIT Now로 시작
- 2016-2017: Now.sh 플랫폼 구축
- 2018-2019: Serverless 전환
- 2020: Vercel로 리브랜딩, Next.js 통합
- 2021-2022: Edge Functions 출시
- 2023-현재: AI SDK, v0 출시
🧩 기술 스택 상세
Build Platform
빌드 시스템 (Go)
// Vercel의 빌드 파이프라인
package builder
import (
"context"
"fmt"
"time"
"github.com/vercel/builder/pkg/analyzer"
"github.com/vercel/builder/pkg/cache"
"github.com/vercel/builder/pkg/runtime"
)
type BuildPipeline struct {
analyzer *analyzer.FrameworkAnalyzer
cache *cache.BuildCache
registry *Registry
telemetry *Telemetry
}
type BuildRequest struct {
RepoURL string
Branch string
CommitSHA string
ProjectID string
Environment map[string]string
}
func (bp *BuildPipeline) Build(ctx context.Context, req BuildRequest) (*BuildResult, error) {
span := trace.StartSpan("build.pipeline")
defer span.End()
// 1. 소스 코드 클론
sourceDir, err := bp.cloneRepository(ctx, req)
if err != nil {
return nil, fmt.Errorf("clone failed: %w", err)
}
defer cleanup(sourceDir)
// 2. 프레임워크 감지
framework, err := bp.analyzer.Detect(sourceDir)
if err != nil {
return nil, fmt.Errorf("framework detection failed: %w", err)
}
span.SetAttributes(
attribute.String("framework", framework.Name),
attribute.String("version", framework.Version),
)
// 3. 캐시 복원
cacheKey := bp.generateCacheKey(req, framework)
if cached := bp.cache.Restore(cacheKey); cached != nil {
span.AddEvent("cache_hit")
sourceDir = bp.applyCachedDependencies(sourceDir, cached)
}
// 4. 빌드 실행
buildResult, err := bp.executeBuild(ctx, sourceDir, framework, req.Environment)
if err != nil {
return nil, fmt.Errorf("build execution failed: %w", err)
}
// 5. 최적화
optimized, err := bp.optimize(buildResult, framework)
if err != nil {
span.RecordError(err)
// 최적화 실패는 무시하고 진행
optimized = buildResult
}
// 6. 아티팩트 업로드
artifacts, err := bp.uploadArtifacts(ctx, optimized)
if err != nil {
return nil, fmt.Errorf("artifact upload failed: %w", err)
}
// 7. 캐시 저장
bp.cache.Save(cacheKey, bp.extractCacheableAssets(optimized))
// 8. 메타데이터 생성
metadata := bp.generateMetadata(framework, buildResult, artifacts)
return &BuildResult{
Artifacts: artifacts,
Metadata: metadata,
Duration: time.Since(startTime),
CacheHit: cached != nil,
}, nil
}
// 프레임워크별 빌드 실행
func (bp *BuildPipeline) executeBuild(
ctx context.Context,
sourceDir string,
framework Framework,
env map[string]string,
) (*BuildOutput, error) {
// 런타임 선택
rt := runtime.GetRuntime(framework)
// 병렬 처리를 위한 워커 풀
workers := runtime.NewWorkerPool(runtime.MaxWorkers())
defer workers.Close()
// 빌드 태스크 생성
tasks := rt.CreateBuildTasks(sourceDir, framework)
results := make([]*TaskResult, len(tasks))
errChan := make(chan error, len(tasks))
// 병렬 실행
for i, task := range tasks {
workers.Submit(func(idx int, t BuildTask) func() {
return func() {
result, err := t.Execute(ctx, env)
if err != nil {
errChan <- fmt.Errorf("task %s failed: %w", t.Name, err)
return
}
results[idx] = result
}
}(i, task))
}
// 결과 수집
workers.Wait()
close(errChan)
// 에러 확인
for err := range errChan {
if err != nil {
return nil, err
}
}
// 결과 병합
return rt.MergeResults(results), nil
}
// 빌드 최적화
func (bp *BuildPipeline) optimize(output *BuildOutput, framework Framework) (*BuildOutput, error) {
optimized := &BuildOutput{
Files: make([]File, 0, len(output.Files)),
}
for _, file := range output.Files {
switch {
case isJavaScript(file):
// Terser로 minification
minified, err := bp.minifyJS(file)
if err == nil {
file = minified
}
case isCSS(file):
// CSS 최적화
optimized, err := bp.optimizeCSS(file)
if err == nil {
file = optimized
}
case isImage(file):
// 이미지 최적화 (WebP 변환 등)
converted, err := bp.optimizeImage(file)
if err == nil {
file = converted
}
}
optimized.Files = append(optimized.Files, file)
}
// Tree shaking
if framework.SupportsTreeShaking {
optimized = bp.performTreeShaking(optimized)
}
// 번들 분석
optimized.BundleAnalysis = bp.analyzeBundles(optimized)
return optimized, nil
}
Edge Runtime (Rust)
// Vercel Edge Runtime 구현
use std::time::Duration;
use tokio::time::timeout;
use wasmtime::{Engine, Module, Store};
pub struct EdgeRuntime {
engine: Engine,
isolate_pool: IsolatePool,
request_router: RequestRouter,
cache: EdgeCache,
}
#[derive(Debug)]
pub struct EdgeFunction {
id: String,
wasm_module: Module,
config: FunctionConfig,
cold_start_snapshot: Option<Snapshot>,
}
impl EdgeRuntime {
pub async fn handle_request(
&self,
request: Request<Body>,
function_id: &str,
) -> Result<Response<Body>, Error> {
// 1. 함수 조회
let function = self.get_function(function_id)
.ok_or(Error::FunctionNotFound)?;
// 2. 캐시 확인
if let Some(cached) = self.check_cache(&request, &function.config).await? {
return Ok(cached);
}
// 3. Isolate 가져오기 (풀에서)
let mut isolate = self.isolate_pool.acquire().await?;
// 4. Cold start 최적화
if let Some(snapshot) = &function.cold_start_snapshot {
isolate.restore_from_snapshot(snapshot)?;
} else {
// 첫 실행 - 모듈 인스턴스화
let instance = isolate.instantiate(&function.wasm_module)?;
// 스냅샷 생성 (다음 cold start용)
let snapshot = isolate.create_snapshot()?;
self.cache_snapshot(function_id, snapshot).await;
}
// 5. 요청 실행 (타임아웃 포함)
let response = timeout(
Duration::from_millis(function.config.timeout_ms),
self.execute_function(&mut isolate, request),
).await??;
// 6. 응답 캐싱
if should_cache(&response, &function.config) {
self.cache_response(&request, &response, &function.config).await?;
}
// 7. Isolate 반환
self.isolate_pool.release(isolate);
Ok(response)
}
async fn execute_function(
&self,
isolate: &mut Isolate,
request: Request<Body>,
) -> Result<Response<Body>, Error> {
// Request 객체를 WASM 메모리로 전달
let request_ptr = isolate.serialize_request(request)?;
// 함수 호출
let func = isolate.get_export::<Func>("handler")?;
let response_ptr = func.call(&mut isolate.store, &[request_ptr.into()])?;
// Response 역직렬화
let response = isolate.deserialize_response(response_ptr)?;
Ok(response)
}
}
// Isolate Pool 관리
pub struct IsolatePool {
available: Arc<Mutex<Vec<Isolate>>>,
max_size: usize,
memory_limit: usize,
}
impl IsolatePool {
pub async fn acquire(&self) -> Result<Isolate, Error> {
// 사용 가능한 isolate 확인
if let Some(isolate) = self.available.lock().await.pop() {
return Ok(isolate);
}
// 새 isolate 생성
self.create_isolate().await
}
async fn create_isolate(&self) -> Result<Isolate, Error> {
let mut config = Config::new();
config.wasm_simd(true);
config.wasm_bulk_memory(true);
config.wasm_multi_value(true);
// 메모리 제한
config.max_wasm_stack(1024 * 1024); // 1MB stack
config.memory_limit(self.memory_limit);
// V8 스타일 isolate 생성
let engine = Engine::new(&config)?;
let store = Store::new(&engine, ());
Ok(Isolate::new(engine, store))
}
}
// 지능형 라우팅
pub struct RequestRouter {
rules: Vec<RoutingRule>,
geo_database: GeoIPDatabase,
}
impl RequestRouter {
pub fn route(&self, request: &Request<Body>) -> RouterDecision {
let client_ip = extract_client_ip(request);
let geo_info = self.geo_database.lookup(client_ip);
// 가장 가까운 엣지 로케이션 선택
let nearest_edge = self.find_nearest_edge(&geo_info);
// 커스텀 라우팅 규칙 적용
for rule in &self.rules {
if rule.matches(request, &geo_info) {
return rule.decision.clone();
}
}
RouterDecision::Edge(nearest_edge)
}
}
Frontend (Next.js Integration)
프레임워크 최적화
// Vercel의 Next.js 최적화
import { NextConfig } from "next";
import { VercelConfig } from "@vercel/static-config";
export class NextJSOptimizer {
private config: NextConfig;
private vercelConfig: VercelConfig;
async optimize(projectPath: string): Promise<OptimizedBuild> {
// 1. 설정 로드
this.config = await this.loadNextConfig(projectPath);
this.vercelConfig = await this.loadVercelConfig(projectPath);
// 2. 자동 최적화 적용
const optimizations = [];
// 이미지 최적화
if (this.config.images) {
optimizations.push(this.optimizeImages());
}
// 폰트 최적화
optimizations.push(this.optimizeFonts());
// 번들 분석 및 최적화
optimizations.push(this.optimizeBundles());
// ISR 설정
if (this.hasISRPages()) {
optimizations.push(this.configureISR());
}
// Edge Functions 변환
if (this.hasEdgeAPI()) {
optimizations.push(this.convertToEdge());
}
// 병렬 실행
const results = await Promise.all(optimizations);
return this.mergeOptimizations(results);
}
private async optimizeImages(): Promise<ImageOptimization> {
const images = await this.findAllImages();
return Promise.all(
images.map(async (image) => {
// Next/Image 컴포넌트로 자동 변환
if (this.isPlainImg(image)) {
await this.convertToNextImage(image);
}
// 원본 이미지 최적화
const optimized = await this.processImage(image, {
formats: ["webp", "avif"],
sizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
quality: this.config.images?.quality || 75,
});
return optimized;
}),
);
}
private async optimizeBundles(): Promise<BundleOptimization> {
// Webpack 설정 분석
const webpackConfig = this.extractWebpackConfig();
// 자동 코드 스플리팅
const splitChunks = {
chunks: "all",
cacheGroups: {
default: false,
vendors: false,
framework: {
name: "framework",
chunks: "all",
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 40,
enforce: true,
},
commons: {
name: "commons",
chunks: "all",
minChunks: 2,
priority: 20,
},
shared: {
name(module, chunks) {
return crypto
.createHash("sha1")
.update(chunks.reduce((acc, chunk) => acc + chunk.name, ""))
.digest("hex");
},
priority: 10,
minChunks: 2,
reuseExistingChunk: true,
},
},
};
// Tree shaking 강화
const optimization = {
usedExports: true,
sideEffects: false,
concatenateModules: true,
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
parse: { ecma: 8 },
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
drop_console: true,
drop_debugger: true,
},
mangle: { safari10: true },
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
}),
],
};
return { splitChunks, optimization };
}
private async configureISR(): Promise<ISRConfig> {
const pages = await this.findAllPages();
const isrPages = [];
for (const page of pages) {
const { revalidate } = await this.extractPageConfig(page);
if (revalidate) {
isrPages.push({
path: page.path,
revalidate,
// 스마트 무효화 설정
dependencies: await this.analyzeDependencies(page),
fallback: this.determineFallback(page),
});
}
}
return {
pages: isrPages,
// 글로벌 캐시 설정
cache: {
strategy: "stale-while-revalidate",
maxAge: 31536000, // 1년
staleWhileRevalidate: 86400, // 1일
},
};
}
}
// Analytics 수집
export class VercelAnalytics {
private queue: AnalyticsEvent[] = [];
private flushInterval: number = 5000;
track(event: AnalyticsEvent): void {
this.queue.push({
...event,
timestamp: Date.now(),
sessionId: this.getSessionId(),
visitorId: this.getVisitorId(),
});
if (this.queue.length >= 10) {
this.flush();
}
}
private async flush(): Promise<void> {
if (this.queue.length === 0) return;
const events = [...this.queue];
this.queue = [];
try {
await fetch("/_vercel/insights/event", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ events }),
});
} catch (error) {
// 실패한 이벤트는 다시 큐에 추가
this.queue.unshift(...events);
}
}
measureWebVitals(): void {
// Core Web Vitals 측정
if ("PerformanceObserver" in window) {
// LCP (Largest Contentful Paint)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.track({
type: "web-vital",
name: "LCP",
value: lastEntry.renderTime || lastEntry.loadTime,
});
}).observe({ entryTypes: ["largest-contentful-paint"] });
// FID (First Input Delay)
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
this.track({
type: "web-vital",
name: "FID",
value: entry.processingStart - entry.startTime,
});
});
}).observe({ entryTypes: ["first-input"] });
// CLS (Cumulative Layout Shift)
let clsValue = 0;
new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
this.track({
type: "web-vital",
name: "CLS",
value: clsValue,
});
}).observe({ entryTypes: ["layout-shift"] });
}
}
}
인프라 및 네트워크
글로벌 엣지 네트워크
// Edge Network 관리
export class EdgeNetwork {
private nodes: Map<string, EdgeNode> = new Map();
private healthChecker: HealthChecker;
private loadBalancer: LoadBalancer;
async routeRequest(request: Request): Promise<EdgeNode> {
// 1. 클라이언트 위치 파악
const clientLocation = await this.geolocate(request.ip);
// 2. 가용한 노드 찾기
const availableNodes = await this.getHealthyNodes();
// 3. 최적 노드 선택
const selectedNode = this.selectOptimalNode(
clientLocation,
availableNodes,
request,
);
// 4. 요청 라우팅
return selectedNode;
}
private selectOptimalNode(
clientLocation: GeoLocation,
nodes: EdgeNode[],
request: Request,
): EdgeNode {
// 점수 계산
const scores = nodes.map((node) => ({
node,
score: this.calculateNodeScore(node, clientLocation, request),
}));
// 가장 높은 점수의 노드 선택
scores.sort((a, b) => b.score - a.score);
return scores[0].node;
}
private calculateNodeScore(
node: EdgeNode,
clientLocation: GeoLocation,
request: Request,
): number {
let score = 100;
// 거리 점수 (가장 중요)
const distance = this.calculateDistance(node.location, clientLocation);
score -= distance / 100; // 100km당 1점 감소
// 부하 점수
const load = node.getCurrentLoad();
score -= load * 0.5; // 부하율당 0.5점 감소
// 특수 라우팅 규칙
if (request.headers["x-vercel-ip-country"] === node.country) {
score += 10; // 같은 국가 보너스
}
// 캐시 히트 가능성
if (node.hasInCache(request.url)) {
score += 20; // 캐시 보너스
}
return score;
}
}
// DDoS 방어
export class DDoSProtection {
private rateLimiter: RateLimiter;
private blacklist: Set<string> = new Set();
private suspicious: Map<string, number> = new Map();
async checkRequest(request: Request): Promise<boolean> {
const ip = request.ip;
// 블랙리스트 체크
if (this.blacklist.has(ip)) {
return false;
}
// Rate limiting
const allowed = await this.rateLimiter.check(ip, {
windowMs: 60 * 1000, // 1분
max: 100, // 최대 100 요청
});
if (!allowed) {
this.incrementSuspicion(ip);
return false;
}
// 패턴 분석
if (this.detectMaliciousPattern(request)) {
this.incrementSuspicion(ip);
return false;
}
return true;
}
private detectMaliciousPattern(request: Request): boolean {
// User-Agent 검사
const ua = request.headers["user-agent"];
if (!ua || this.isBotUserAgent(ua)) {
return true;
}
// 요청 패턴 검사
if (this.isScrapingPattern(request)) {
return true;
}
// Payload 크기 검사
if (request.contentLength > 10 * 1024 * 1024) {
// 10MB
return true;
}
return false;
}
private incrementSuspicion(ip: string): void {
const current = this.suspicious.get(ip) || 0;
this.suspicious.set(ip, current + 1);
// 임계값 초과 시 블랙리스트
if (current + 1 >= 10) {
this.blacklist.add(ip);
this.suspicious.delete(ip);
// 일정 시간 후 해제
setTimeout(() => {
this.blacklist.delete(ip);
}, 3600 * 1000); // 1시간
}
}
}
기술 스택 요약:
- Backend: Go, Rust, Node.js
- Frontend: Next.js, React, TypeScript
- Edge Runtime: WebAssembly, V8 Isolates
- 인프라: AWS, Cloudflare, 자체 CDN
- 데이터베이스: PostgreSQL, DynamoDB, Redis
- 모니터링: 자체 분석 플랫폼
- 빌드 도구: Turborepo, Webpack, SWC
💡 핵심 기술 인사이트
1. 엣지 컴퓨팅의 미래
- 사용자 가까이에서 코드 실행
- 콜드 스타트 최소화
- 글로벌 일관된 성능
2. 개발자 경험 혁신
- Git push만으로 배포
- 즉각적인 미리보기
- 프레임워크 자동 감지
3. 성능 최적화 자동화
- 이미지 자동 최적화
- 코드 스플리팅
- 캐싱 전략 자동화
📈 성과 & 지표
기술적 성과
- 배포 시간: Git push → 프로덕션 < 60초
- 빌드 성공률: 99.5%+
- 엣지 응답시간: P50 < 50ms
- 가용성: 99.99%
- 글로벌 커버리지: 100+ PoP
비즈니스 영향
- 개발자 생산성: 3배 향상
- 페이지 로드 속도: 평균 40% 개선
- SEO 점수: 평균 20점 상승
- 개발 비용: 60% 절감
🎓 Vercel에서 배울 점
✅ 적용 가능한 패턴
- Preview Deployments: PR별 미리보기
- Edge Functions: 엣지에서 실행
- 자동 최적화: 프레임워크별 최적화
- Analytics: 실시간 성능 모니터링
- Rollback: 즉시 이전 버전으로
❌ 주의사항
- 벤더 종속: Vercel 전용 기능
- 비용: 트래픽 증가 시 비용 상승
- 제한사항: 함수 실행 시간 제한
- 커스터마이징: 제한된 설정
📚 추천 리소스
🔮 미래 전망
현재 집중 분야
- AI Integration: v0, AI SDK
- Edge Databases: 분산 데이터베이스
- Conformance: 프레임워크 최적화
- Storage: Edge 스토리지 솔루션
- Monitoring: 더 정교한 분석
기술 투자 영역
- WebAssembly: 더 빠른 Edge Runtime
- Machine Learning: 자동 최적화
- 5G Edge: 초저지연 컴퓨팅
- Blockchain: 분산 배포
- IoT: 엣지 디바이스 지원
"우리는 프론트엔드 클라우드를 구축하여, 개발자가 최고의 사용자 경험을 만드는 데 집중할 수 있도록 돕고 있습니다."
- Vercel Engineering
마지막 업데이트: 2025-01-28