Skip to Content
WebReactReact 실무20. GitHub Actions로 CI 구성

이번 편의 결과물: PR을 올리면 GitHub Actions가 자동으로 lint·테스트·빌드를 실행하고 결과가 PR에 체크로 표시됩니다. · 다루는 개념: 워크플로 작성(.github/workflows/), lint·단위/통합 테스트·빌드 자동 실행, PR 상태 체크

이 편에서 만드는 파일

bookshelf/ ├── .github/ │ └ workflows/ │ └ ci.yml ← + lint·test·build 워크플로 └── package.json ← ~ lint 스크립트 확인, ci 스크립트 추가

개념 정리

CI가 검사의 관문이 되는 구조

CI(Continuous Integration, 지속적 통합)는 코드 변경마다 자동으로 빌드·검증하는 관행입니다. GitHub Actions는 저장소 이벤트(push, PR)에 반응해 워크플로(workflow)라는 자동화 스크립트를 실행합니다. 워크플로는 .github/workflows/ 아래 YAML 파일로 정의합니다.

개념
workflow하나의 YAML 파일. 언제(on) 무엇을(jobs) 실행할지 정의
job워크플로 안의 독립된 실행 단위. 기본적으로 병렬 실행
stepjob 안에서 순서대로 실행되는 명령 하나하나
action재사용 가능한 step 묶음(actions/checkout@v4 등)

npm ci와 npm install의 차이

CI 환경에서는 npm install 대신 npm ci를 씁니다. npm cipackage-lock.json에 잠긴 버전을 정확히 그대로 설치하고, node_modules가 이미 있으면 지우고 새로 설치합니다. “내 컴퓨터에서는 되는데 CI에서는 실패하는” 버전 불일치 문제를 줄입니다.

실패하면 병합을 막는다

워크플로의 어떤 step이든 0이 아닌 종료 코드로 끝나면 job이 실패로 표시됩니다. GitHub 저장소 설정에서 Settings → Branches필수 상태 체크(required status check)로 이 워크플로를 지정하면, 실패한 PR은 main에 병합할 수 없습니다. 테스트를 작성하는 것과 그 테스트를 병합의 관문으로 세우는 것은 별개의 설정입니다.

실습

1. package.json에 lint·test·build 스크립트 확인하기

// package.json (scripts 발췌) { "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", "lint": "eslint . --max-warnings=0", "test": "vitest run", "test:e2e": "playwright test" } }

testvitest run으로 고정합니다(감시 모드 vitest는 CI에서 종료되지 않아 워크플로가 멈춥니다). test:e2e는 16편에서 만든 Playwright 스위트로, 이번 CI에는 시간이 오래 걸려 넣지 않고 별도 워크플로로 분리합니다(직접 해보기에서 확장).

2. 워크플로 파일 작성하기

# .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: lint-test-build: runs-on: ubuntu-latest steps: - name: 저장소 코드 가져오기 uses: actions/checkout@v4 - name: Node 22 설정 uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: 의존성 설치 run: npm ci - name: 린트 검사 run: npm run lint - name: 단위·통합 테스트 run: npm test - name: 프로덕션 빌드 run: npm run build env: VITE_API_BASE_URL: http://localhost:3001

on.pull_requestbranches: [main]을 지정해, main으로 향하는 PR마다 이 워크플로가 돈다는 뜻입니다. cache: npmsetup-nodepackage-lock.json 해시를 기준으로 의존성을 캐시해 반복 실행 속도를 높여 줍니다. 빌드 step의 env는 19편에서 만든 환경 변수를 CI 안에서도 주입합니다. 이 값은 빌드 시점에 번들에 박히기만 할 뿐 CI가 실제로 그 주소에 접속하지는 않으므로, 서버가 떠 있지 않아도 빌드는 통과합니다. 로컬 .env.production 파일은 CI에 올라가지 않으므로, 값을 직접 넘기거나 GitHub 저장소의 Secrets and variables 설정에 등록한 값을 참조합니다.

3. 커밋하고 PR 만들어 확인하기

git checkout -b ci/github-actions git add .github/workflows/ci.yml package.json git commit -m "ci: lint, test, build 워크플로 추가" git push -u origin ci/github-actions

GitHub에서 이 브랜치로 PR을 엽니다. PR 화면 하단에 lint-test-build 체크가 나타나고, 잠시 후 성공(초록 체크) 또는 실패(빨간 X)로 바뀝니다. 일부러 npm run lint가 실패하도록 코드에 미사용 변수를 하나 남기고 push해, 체크가 실패로 바뀌는 것과 PR에 “이 브랜치에는 실패한 체크가 있습니다” 경고가 뜨는 것을 확인합니다. 확인 후 되돌립니다.

4. 필수 상태 체크로 지정하기

저장소 Settings → Branches → Branch protection rules에서 main에 대한 규칙을 추가하고, Require status checks to pass before merging을 켠 뒤 lint-test-build를 선택합니다. 이제부터 체크가 실패한 PR은 Merge 버튼이 비활성화됩니다.

직접 해보기

Playwright E2E 테스트를 별도 워크플로(.github/workflows/e2e.yml)로 분리하고, main에 push할 때만 실행되도록 만들어 보세요.

on: push: branches: [main]만 남기고, actions/checkout 이후 npx playwright install --with-deps step을 추가해 브라우저를 설치한 다음 npm run test:e2e를 실행합니다. PR마다 돌리기엔 느리므로 main 반영 시점에만 돌리는 것이 실무에서 흔한 절충입니다.

자주 하는 실수

증상원인고치는 법
CI에서 vitest가 끝나지 않고 멈춘다test 스크립트가 감시 모드(vitest)로 되어 있음vitest run으로 1회 실행 모드 사용
로컬에서는 되는데 CI 빌드만 실패환경 변수를 CI에 안 넘김워크플로 env 또는 저장소 Secrets에 등록
PR 체크가 안 보인다워크플로의 on 조건에 pull_request가 없음on.pull_request.branches에 대상 브랜치 추가

확인 문제

문제 14지선다
CI 환경에서 npm install 대신 npm ci를 쓰는 이유는 무엇입니까
문제 24지선다
CI에서 vitest run 대신 감시 모드(vitest)를 쓰면 생기는 문제는 무엇입니까
문제 34지선다
필수 상태 체크(required status check)를 지정하면 생기는 효과는 무엇입니까

react_3 마무리 — 완료 기준 점검

20편으로 bookshelf가 실무 수준으로 완성되었습니다. src/ 정본 파일 목록과 01편 학습 방향에서 정한 완료 기준을 하나씩 확인합니다.

bookshelf/src/ ├── main.jsx # RouterProvider + QueryClientProvider + AuthProvider 렌더링 ├── router.jsx # createBrowserRouter, 보호 라우트, 세션 만료 레이아웃 ├── index.css # 전역 스타일, .sr-only, :focus-visible ├── pages/ │ ├── BookListPage.jsx # TanStack Query로 목록 조회, 정렬·필터·페이지네이션 │ ├── BookDetailPage.jsx │ ├── NewBookPage.jsx # react-hook-form + zod, 접근성 보정 │ ├── LoginPage.jsx # 로그인 폼, aria-live 에러 │ └── NotFoundPage.jsx ├── components/ │ ├── Header.jsx, ErrorBoundary.jsx, ProtectedRoute.jsx, SessionWatcher.jsx │ ├── BookTable.jsx, BookRow.jsx # 정렬·필터·페이지네이션, memo 적용 │ ├── Modal.jsx # 포커스 트랩, aria-modal │ └── BookCard.jsx ├── context/{ThemeContext,AuthContext}.jsx ├── hooks/{useBooks,useBookMutations,useLocalStorage,useDebounce}.js ├── store/useUiStore.js # Zustand, selector 기반 구독 ├── api/{client,books,auth}.js # 환경 변수 기반 API 클라이언트(json-server) ├── schemas/bookSchema.js # zod 검증 스키마 ├── i18n/index.js, i18n/locales/{ko,en}.json # react-i18next 리소스 ├── mocks/{handlers,server,browser}.js # 14~16편 테스트 전용(프로덕션 코드에서 import 안 함) ├── utils/onRenderCallback.js ├── test/ # Vitest + RTL 단위·통합 테스트 └── e2e/ # Playwright E2E
완료 기준(01편 학습 목표)확인 방법
로그인 후 책을 검색·정렬·페이지네이션으로 조회json-server 실행 후 npm run preview에서 로그인 → / 조작
표지 이미지와 함께 등록·수정·삭제책 등록 폼에서 이미지 업로드 후 저장 확인
언어를 한국어/영어로 전환헤더의 언어 전환 버튼 클릭
단위·통합·E2E 테스트 모두 통과npm test, npm run test:e2e
Vercel(또는 GitHub Pages)에 배포되어 실제 URL로 접속19편에서 발급된 URL 접속(API 기능은 로컬 json-server 필요)
GitHub Actions가 PR마다 lint·테스트·빌드 자동 실행PR을 열어 체크 상태 확인

여섯 항목이 모두 통과하면 bookshelf는 튜토리얼이 아니라 동작하는 배포된 앱 + 테스트 스위트 + CI로 완성된 것입니다. react_1의 컴포넌트·상태부터 react_3의 서버 상태·인증·성능·접근성·배포·CI까지, 20편에 걸쳐 쌓인 코드가 이 표의 근거입니다.

참고 자료

Last updated on