🏢 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