본문 바로가기

React

React 앱 CI/CD 파이프라인 구축하기

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로 확장하는 전략을 권장합니다.