React 앱을 안정적으로 릴리스하려면 자동화된 CI/CD 파이프라인이 필수입니다. 이 글은 GitHub Actions 기준으로 테스트, 품질 검사, 빌드, 그리고 Vercel/Netlify, AWS S3+CloudFront, Docker 배포까지 한 번에 구성하는 실무형 가이드를 제공합니다.
1. 목표와 아키텍처 개요
목표는 다음과 같습니다. PR마다 테스트/품질 게이트를 통과한 코드만 main에 머지되며, main에 푸시되면 자동으로 빌드/배포가 실행됩니다. 배포 대상은 상황에 따라 Vercel/Netlify(서버리스), AWS S3+CloudFront(정적 호스팅), Docker 이미지(온프레미스/쿠버네티스) 중 선택합니다.
2. 프로젝트 준비 (스크립트/도구 표준화)
Node LTS(18/20), 패키지 매니저(pnpm/yarn)와 기본 스크립트를 통일합니다. Vite 기반 React 예시입니다.
{
"name": "react-app",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 4173",
"lint": "eslint . --ext .ts,.tsx --max-warnings=0",
"type-check": "tsc --noEmit",
"test": "vitest run --coverage"
},
"devDependencies": {
"typescript": "^5.4.0",
"vite": "^5.0.0",
"vitest": "^1.5.0",
"eslint": "^9.0.0"
}
}
팁: .nvmrc, .npmrc/.pnpmfile.cjs, browserslist, tsconfig.strict 등 베이스 설정을 리포 표준으로 맞춥니다.
3. GitHub Actions로 CI 파이프라인 구성
다음 워크플로우는 PR/Push 시 테스트/품질 검사, 빌드, 아티팩트 업로드까지 수행합니다.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
- uses: pnpm/action-setup@v3
with:
version: 9
run_install: true
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm type-check
- name: Unit test
run: pnpm test
- name: Build
run: pnpm build
- name: Upload build artifact
if: matrix.node == '20' && github.event_name == 'push'
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
핵심 포인트: Node 버전 매트릭스와 캐시를 활용해 호환성과 속도를 확보하고, PR에는 배포를 막고 main 푸시에만 아티팩트를 남깁니다.
4. 품질 게이트 강화 (테스트/린트/타입/프리뷰)
필수 체크리스트입니다.
- 테스트: Vitest/Jest 커버리지 임계치 설정(예: 최소 70%).
- 린트/포맷: ESLint max-warnings=0, Prettier 병행.
- 타입 안정성: tsc --noEmit 또는 프로젝트 레퍼런스 사용.
- PR 미리보기: Vercel/Netlify Preview를 활성화하여 리뷰 품질 향상.
5. 빌드/캐시 최적화
의존성 캐시: actions/setup-node의 cache 옵션을 활용합니다. pnpm은 전역 스토어 캐시로 빠릅니다. 빌드 캐시는 Vite/CRA는 제한적이니, 모노레포는 Turborepo/PNPM 워크스페이스로 빌드 캐시를 고려합니다. Node/gcc 등 툴체인 버전을 고정해 캐시 적중률을 높입니다.
6. 배포 예시 1: AWS S3 + CloudFront (정적 호스팅)
정적 React 빌드(dist)를 S3에 업로드하고 CloudFront 캐시를 무효화합니다. AWS 권한은 OIDC를 권장하지만 초반에는 액세스 키로 시작할 수 있습니다.
# .github/workflows/deploy-s3.yml
name: Deploy S3
on:
push:
branches: [main]
jobs:
deploy-s3:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: Sync to S3
run: |
aws s3 sync dist s3://my-react-site --delete \
--cache-control "public, max-age=31536000, immutable"
# HTML은 캐시 짧게
aws s3 cp dist/index.html s3://my-react-site/index.html \
--cache-control "no-cache, no-store, must-revalidate" --content-type text/html
- name: Invalidate CloudFront
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} --paths "/*"
팁: S3 버저닝을 켜두면 롤백이 쉽습니다. index.html은 캐시를 짧게 설정하고 정적 자산에는 해시+장기 캐시를 적용합니다.
7. 배포 예시 2: Vercel/Netlify (프리뷰 포함)
Vercel/Netlify는 리포 연결만으로 PR 프리뷰와 main 자동 배포가 됩니다. CI에서는 테스트만 수행하고 배포는 플랫폼에 맡기는 구성이 단순합니다. 필요 시 CLI로 Actions에서 강제 배포할 수도 있습니다.
# Netlify CLI 배포 예시 (선택)
- name: Netlify Deploy
if: github.ref == 'refs/heads/main'
run: |
npx netlify-cli deploy --dir=dist --prod \
--auth=${{ secrets.NETLIFY_AUTH_TOKEN }} --site=${{ secrets.NETLIFY_SITE_ID }}
# Vercel CLI 배포 예시 (선택)
- name: Vercel Deploy
if: github.ref == 'refs/heads/main'
run: |
npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
npx vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
npx vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
팁: PR마다 Preview URL을 생성해 디자이너/기획자 검수 속도를 높입니다.
8. 배포 예시 3: Docker 이미지 (Nginx 정적 서빙)
온프레미스/쿠버네티스 환경이면 Docker로 정적 파일을 Nginx에서 서빙합니다.
# Dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm i --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# 예시 nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
location / {
try_files $uri /index.html;
}
location ~* \.(js|css|png|jpg|svg)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
GitHub Actions에서 빌드/푸시 후 배포 대상(레지스트리/K8s)에 적용합니다.
# .github/workflows/docker.yml (요약)
- name: Build and push
run: |
docker build -t ghcr.io/${{ github.repository }}:$(git rev-parse --short HEAD) .
echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push ghcr.io/${{ github.repository }}:$(git rev-parse --short HEAD)
9. 환경 변수와 시크릿 관리
클라이언트 사용 변수는 Vite 기준 VITE_ 프리픽스를 사용합니다. 민감 정보는 절대 번들에 포함하지 않습니다.
- GitHub Secrets: API 키, AWS 크리덴셜, Vercel/Netlify 토큰 저장.
- 빌드 타임 변수: CI에서 .env.production 생성 or 플랫폼 대시보드 설정.
- AWS는 OIDC 연동으로 액세스 키 없이 역할 부여를 권장합니다.
# .github/workflows/ci.yml 일부
- name: Create env file
run: |
echo "VITE_API_BASE=${{ secrets.API_BASE }}" >> .env.production
10. 롤백 전략과 버전 태깅
아티팩트와 이미지에 커밋 SHA/태그를 남깁니다. S3는 버저닝/폴더 스위칭, Vercel/Netlify는 이전 배포로 즉시 롤백이 가능합니다. Docker는 레지스트리 태그 교체로 롤백합니다.
- Git 태그: release-YYYYMMDD.N
- 체인지로그 자동화: Changesets/semantic-release
- 릴리스 노트: GitHub Releases와 링크
11. 모노레포/매트릭스와 병렬화
모노레포는 Turborepo/PNPM 워크스페이스로 변경 영향만 빌드합니다. 매트릭스로 Node 18/20을 테스트하고, E2E(Cypress/Playwright)는 병렬 샤딩으로 시간을 절약합니다.
strategy:
matrix:
project: [web, docs]
steps:
- name: Selective build
run: pnpm -w turbo run build --filter=${{ matrix.project }}
12. 최종 체크리스트
- PR: lint/type/test 필수, Preview 링크 자동 첨부
- main: 빌드 성공 시만 배포, 실패 시 자동 알림
- 정적 자산 캐시 정책과 index.html 캐시 분리
- 시크릿/권한 최소화, 감사 로그 확인
- 아티팩트/이미지에 커밋 SHA 태깅 및 롤백 시나리오 문서화
위 구성을 그대로 복사해 프로젝트에 적용하면 하루 만에 신뢰 가능한 React CI/CD 파이프라인을 갖출 수 있습니다. 작은 팀은 Vercel/Netlify로 단순하게, 규모가 커지면 S3+CloudFront 또는 Docker/K8s로 확장하는 전략을 권장합니다.
'React' 카테고리의 다른 글
| React에서 IndexedDB를 이용한 오프라인 데이터 저장 (1) | 2026.05.13 |
|---|---|
| React에서 컴포넌트 성능 측정 및 분석하기 (1) | 2026.05.13 |
| React 앱에서 사용자 권한(Role-Based Access Control) 처리하기 (0) | 2026.05.12 |
| React에서 Progressive Image Loading 구현하기 (0) | 2026.05.11 |
| React 앱에서 애니메이션을 위해 Framer Motion 활용하기 (0) | 2026.05.11 |