CI/CD가 해결하는 문제
"내 로컬에서는 되는데요", 개발자라면 누구나 한 번쯤 들어봤을 말입니다. 코드가 개인 머신에서 동작하는 것과 실제 프로덕션에 배포되는 것 사이에는 많은 단계가 있습니다.
수동으로 이 과정을 반복하면 문제가 발생합니다.
- 빌드 누락 , "빌드 깨지는 거 몰랐어요" → 배포 후 500 에러
- 테스트 스킵 , "급한 핫픽스라서..." → 기존 기능이 깨짐
- 린트 무시 , "나중에 고칠게요" → 코드 품질 하락
- 배포 실수 , 스테이징 대신 프로덕션에 배포
CI (Continuous Integration)는 코드 변경을 자동으로 검증하고, CD (Continuous Deployment)는 검증된 코드를 자동으로 배포합니다. 사람의 실수를 시스템이 방지합니다.
파이프라인의 단계
프론트엔드 CI/CD 파이프라인은 보통 다음 단계로 구성됩니다.
Push → Install → Lint + Type-check → Test → Build → Deploy → Post-deploy
(병렬 실행) (순차) (순차) (조건부) (병렬)
캐시 복원 Lighthouse + Bundle check각 단계는 이전 단계의 성공을 전제로 합니다. 하나라도 실패하면 파이프라인이 중단됩니다, 빠른 실패 (fail fast) 원칙입니다.
GitHub Actions 기본 구조
가장 널리 쓰이는 CI/CD 플랫폼인 GitHub Actions의 구조를 살펴봅니다.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit
test:
needs: [lint, type-check]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
build:
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/핵심 포인트:
lint와type-check는 서로 독립적이므로 병렬 실행test는needs: [lint, type-check]로 둘 다 통과한 후 실행build는needs: [test]로 테스트 통과 후 실행--frozen-lockfile로 lockfile과 다른 의존성 설치를 방지
캐싱 전략
CI에서 가장 많은 시간을 잡아먹는 것은 의존성 설치 입니다. 캐싱으로 이 시간을 줄입니다.
node_modules 캐싱
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm' # pnpm store를 자동 캐싱actions/setup-node는 패키지 매니저의 캐시를 자동으로 관리합니다. lockfile의 해시를 키로 사용하므로, 의존성이 변경되지 않으면 캐시에서 복원합니다.
빌드 캐싱
Next.js의 경우 .next/cache를 캐싱하면 재빌드 시간이 크게 줄어듭니다.
- uses: actions/cache@v4
with:
path: .next/cache
key: nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ hashFiles('pnpm-lock.yaml') }}-Turborepo 리모트 캐시
모노레포에서는 Turborepo의 리모트 캐시가 가장 효과적입니다.
- run: pnpm turbo run build --token=${{ secrets.TURBO_TOKEN }} --team=${{ vars.TURBO_TEAM }}한 PR의 빌드 결과를 다른 PR과 공유할 수 있습니다.
Preview 배포
PR마다 고유한 URL로 배포하는 것이 Preview 배포입니다. Vercel, Netlify, Cloudflare Pages 같은 플랫폼은 이를 자동으로 지원합니다.
PR #42: feat/new-header
→ https://my-app-pr-42.vercel.app
PR #57: fix/login-bug
→ https://my-app-pr-57.vercel.appPreview 배포의 장점:
- 코드 리뷰와 시각적 확인을 동시에 , PR 코멘트에 Preview URL이 자동으로 달림
- QA 팀이 머지 전에 테스트 , 별도 환경 세팅 없이 URL만 공유
- 디자이너와 빠른 피드백 루프 , "이 URL 확인해주세요"
# Vercel의 경우 별도 설정 없이 자동
# 수동 구성이 필요한 경우:
deploy-preview:
if: github.event_name == 'pull_request'
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- run: npx vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}배포 전략
프로덕션 배포 시 다운타임을 최소화하는 전략이 필요합니다.
롤링 배포 (Rolling)
기존 인스턴스를 하나씩 새 버전으로 교체 합니다. 교체 중에는 구 버전과 신 버전이 공존합니다.
[v1] [v1] [v1] [v1] ← 배포 시작
[v2] [v1] [v1] [v1] ← 1개 교체
[v2] [v2] [v1] [v1] ← 2개 교체
[v2] [v2] [v2] [v2] ← 완료- 장점 : 리소스 추가 없이 점진적 배포
- 단점 : 배포 중 두 버전이 공존 (API 호환성 주의)
블루-그린 배포 (Blue-Green)
동일한 환경 두 개를 운영합니다. 새 버전을 Green에 배포하고, 트래픽을 한 번에 전환합니다.
Blue (v1) ← 트래픽 Green (v2) ← 대기
Blue (v1) ← 대기 Green (v2) ← 트래픽 전환- 장점 : 즉시 롤백 가능 (트래픽을 다시 Blue로 전환)
- 단점 : 인프라 비용 2배
카나리 배포 (Canary)
트래픽의 일부 (예: 5%)만 새 버전으로 보내고, 문제가 없으면 점진적으로 늘립니다.
v1: 100% → 95% → 80% → 50% → 0%
v2: 0% → 5% → 20% → 50% → 100%- 장점 : 위험 최소화, 실제 사용자로 검증
- 단점 : 라우팅 설정의 복잡도, 모니터링 필수
프론트엔드에서는 CDN 레벨에서 카나리를 구현하거나, Vercel의 Skew Protection 같은 기능을 활용합니다.
프론트엔드 특화, Post-deploy 검증
프론트엔드에는 백엔드와 다른 품질 지표가 있습니다.
Lighthouse CI
배포 후 자동으로 Lighthouse를 실행하여 Core Web Vitals를 측정합니다.
lighthouse:
needs: [deploy]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: treosh/lighthouse-ci-action@v12
with:
urls: |
https://my-app.com/
https://my-app.com/dashboard
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true// lighthouse-budget.json
[{
"path": "/*",
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "interactive", "budget": 3500 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "total", "budget": 500 }
]
}]LCP가 2.5초를 초과하면 CI가 실패합니다. 성능 저하를 배포 전에 잡습니다.
번들 사이즈 체크
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}PR에 번들 사이즈 변화를 자동으로 코멘트합니다.
📦 Size limit report
Package Size Change
main.js 142 kB +3.2 kB (+2.3%)
vendor.js 89 kB 0 B시리즈 마무리, 프론트엔드 인프라 전체 지도
7편에 걸쳐 프론트엔드 인프라의 핵심 주제를 다뤘습니다.
| 편 | 주제 | 핵심 키워드 |
|---|---|---|
| 1 | 트랜스파일러 | Babel, SWC, TypeScript |
| 2 | 린터와 포매터 | ESLint, Prettier, AST |
| 3 | 번들러 | Webpack, Vite, esbuild |
| 4 | Webpack vs Vite | HMR, Dev Server, 빌드 속도 |
| 5 | 패키지 매니저 | npm, pnpm, Yarn PnP |
| 6 | 모노레포 | Turborepo, Nx, Workspaces |
| 7 | CI/CD 파이프라인 | GitHub Actions, 배포 전략 |
이 인프라들은 독립적이 아니라 하나의 파이프라인으로 연결 됩니다.
코드 작성 → 린터/포매터 → 트랜스파일 → 번들링 → 테스트 → 빌드 → 배포
패키지 매니저 ESLint SWC/Babel Vite/Webpack CI/CD
(pnpm) Prettier Tree-shaking GitHub Actions
(모노레포: Turborepo/Nx가 이 전체를 오케스트레이션)각 도구의 역할을 이해하면, 새로운 도구가 등장해도 어떤 문제를 해결하는 도구인지 즉시 파악할 수 있습니다.
시각화
커밋부터 배포 후 검증까지, CI/CD 파이프라인의 각 단계를 순서대로 봅니다.
개발자가 코드를 커밋하고 원격 저장소에 push합니다. GitHub는 이벤트를 감지하여 워크플로우를 트리거합니다.
체크리스트
- PR마다 lint, type-check, test가 자동으로 실행되는가?
- 의존성 설치에 캐싱을 적용했는가?
- PR에 Preview 배포가 자동으로 생성되는가?
- 프로덕션 배포 시 롤백 전략이 있는가?
- Lighthouse CI로 성능 예산을 설정했는가?
- 번들 사이즈 변화를 PR에서 확인할 수 있는가?
-
--frozen-lockfile플래그로 CI에서 lockfile 검증을 하는가?