Node.js 프로젝트의 Git 협업
한눈에 보기
node_modules와 .env는 제외하고 잠금 파일은 커밋하는 이유부터 npm ci, 기능 브랜치와 풀 리퀘스트 리뷰, 병합 전략까지 Node.js 팀의 표준 Git 협업 방식을 정리한다.
최종 업데이트 2026.09.17
Node.js 프로젝트에서 Git 협업을 잘하려면 저장소에 올릴 파일과 올리지 말아야 할 파일을 구분하는 일부터 시작해야 한다. 의존성 디렉터리인 node_modules와 비밀값이 담긴 환경 변수 파일은 제외하고, 잠금 파일(lockfile)은 반드시 커밋해서 모든 팀원이 같은 의존성 트리를 재현할 수 있게 만든다. 그 토대 위에서 기능 브랜치(feature branch)를 나눠 작업하고 풀 리퀘스트(Pull Request) 리뷰를 거쳐 병합하는 흐름이 현대 Node.js 팀의 표준적인 협업 방식이다.
.gitignore로 버전 관리 대상 정하기
Node.js 프로젝트의 저장소에는 소스 코드, 설정 파일, package.json, 잠금 파일처럼 팀원이 공유해야 하는 파일만 올린다. 반대로 node_modules는 package.json과 잠금 파일만 있으면 언제든 다시 설치할 수 있는 파생물이므로 커밋하지 않는다. 수십만 개의 파일이 들어 있어 저장소를 무겁게 만들고, 운영체제마다 네이티브 모듈의 빌드 결과가 달라 오히려 문제를 일으키기 때문이다.
환경 변수 파일인 .env에는 데이터베이스 비밀번호나 API 키 같은 비밀값이 들어가므로 절대 커밋하면 안 된다. 대신 값이 비어 있는 .env.example 파일을 커밋해서 어떤 변수가 필요한지만 공유하는 방식을 많이 쓴다. 빌드 산출물인 dist나 build 디렉터리, 로그 파일, 커버리지 리포트도 모두 제외 대상이다.
# .gitignore 예시
node_modules/
dist/
coverage/
*.log
.env
.env.local
이미 커밋된 파일은 .gitignore에 추가해도 계속 추적되므로, 추적을 끊으려면 다음처럼 인덱스에서만 제거해야 한다.
git rm -r --cached node_modules
git commit -m "chore: node_modules 추적 제거"
잠금 파일은 왜 반드시 커밋하는가
package.json의 의존성 버전은 보통 캐럿(caret) 범위로 적혀 있어서 설치 시점마다 다른 버전이 내려올 수 있다. 예를 들어 ^4.18.0은 4.x 범위의 최신 버전을 허용하므로, 어제 설치한 팀원과 오늘 설치한 팀원의 실제 버전이 달라질 수 있다. package-lock.json 같은 잠금 파일은 직접 의존성뿐 아니라 전이 의존성까지 정확한 버전과 해시를 고정해서 이 문제를 막는다. 잠금 파일을 커밋해야 "내 컴퓨터에서는 되는데요" 문제를 줄이고, 지속적 통합(Continuous Integration, CI) 환경에서도 동일한 빌드를 재현할 수 있다.
CI나 배포 환경에서는 npm install 대신 npm ci를 쓰는 것이 원칙이다. npm ci는 잠금 파일을 절대 수정하지 않고 그대로 설치하며, package.json과 잠금 파일이 어긋나면 실패로 알려 준다.
# 로컬 개발: 의존성 추가 시 잠금 파일이 갱신된다
npm install express
# CI와 배포: 잠금 파일 그대로 설치
npm ci
잠금 파일에서 병합 충돌이 나면 손으로 고치기보다 package.json 충돌만 먼저 해결한 뒤 npm install을 다시 실행해서 잠금 파일을 재생성하는 편이 안전하다. npm은 잠금 파일의 충돌 마커를 감지해서 자동으로 다시 계산해 주기도 한다.
브랜치 전략과 작업 흐름
대부분의 Node.js 팀은 메인 브랜치를 항상 배포 가능한 상태로 유지하고, 모든 작업을 짧은 수명의 기능 브랜치에서 진행하는 깃허브 플로(GitHub Flow)나 트렁크 기반 개발(trunk-based development)을 따른다. 브랜치 이름은 feature/login-api, fix/token-refresh처럼 종류와 목적이 드러나게 짓는다.
# 최신 메인에서 브랜치 생성
git switch main
git pull origin main
git switch -c feature/<작업-이름>
# 작업 후 원격에 올리고 PR 생성
git add .
git commit -m "feat: 로그인 API 추가"
git push -u origin feature/<작업-이름>
브랜치가 오래 살아 있을수록 충돌 가능성이 커지므로, 하루나 이틀 안에 병합할 수 있는 크기로 작업을 쪼개는 것이 중요하다. 메인이 앞서 나갔다면 git pull --rebase origin main으로 내 브랜치를 최신 상태 위에 다시 얹어 충돌을 미리 해결한다.
코드 리뷰와 PR 병합 전략
풀 리퀘스트는 코드 리뷰와 자동 검증이 만나는 지점이다. PR을 열면 CI가 린트(lint), 타입 검사, 테스트를 자동으로 실행하고, 동료가 코드를 리뷰한 뒤 승인해야 병합할 수 있게 브랜치 보호 규칙을 걸어 두는 것이 일반적이다. 아직 완성되지 않은 작업은 초안(draft) PR로 올려 방향을 먼저 논의할 수도 있다. Node.js 프로젝트라면 husky와 lint-staged로 커밋 전에 포매터와 린터를 자동 실행해서 리뷰가 스타일 지적으로 낭비되지 않게 만드는 경우가 많다.
병합 방식은 히스토리 관리 철학에 따라 선택한다.
| 병합 방식 | 동작 | 장점 | 단점 |
|---|---|---|---|
| 병합 커밋(merge commit) | 브랜치 이력을 그대로 두고 병합 커밋을 추가한다 | 작업 과정이 모두 보존된다 | 히스토리가 복잡해진다 |
| 스쿼시 병합(squash merge) | 브랜치의 모든 커밋을 하나로 합쳐 병합한다 | 메인 히스토리가 PR 단위로 깔끔하다 | 중간 커밋 이력이 사라진다 |
| 리베이스 병합(rebase merge) | 커밋들을 메인 위에 일렬로 다시 쌓는다 | 병합 커밋 없이 선형 이력이 된다 | 커밋 해시가 바뀌고 충돌 해결이 번거로울 수 있다 |
PR 하나가 기능 하나에 대응하도록 관리하는 팀이라면 스쿼시 병합이 히스토리를 읽기 쉽게 만들어 주기 때문에 가장 널리 쓰인다.
면접에서 이렇게 물어본다
- node_modules를 커밋하지 않는 이유와, package-lock.json은 반드시 커밋해야 하는 이유를 각각 설명해 보라.
- npm install과 npm ci의 차이는 무엇이고, CI 환경에서 npm ci를 쓰는 이유는 무엇인가.
- package-lock.json에서 병합 충돌이 발생하면 어떻게 해결하는 것이 안전한가.
- 팀에서 사용해 본 브랜치 전략과 PR 병합 방식(스쿼시, 리베이스, 병합 커밋)을 선택한 근거를 말해 보라.
- .env 파일을 실수로 커밋해서 원격에 푸시했다면 어떤 조치를 해야 하는가.
Git과 협업에서 같이 읽기
- Git 커밋 메시지 컨벤션커밋 메시지의 형식과 어휘를 통일하는 규칙을 다룬다. 제목, 본문, 꼬리말 구조와 컨벤셔널 커밋 타입 체계, commitlint와 husky로 컨벤션을 강제하는 방법까지 정리한다.
- Git Flow, GitHub Flow, GitLab Flow 비교릴리스 중심의 Git Flow, 지속 배포를 위한 GitHub Flow, 환경 브랜치로 배포 단계를 표현하는 GitLab Flow를 비교하고 배포 주기와 제품 형태에 따른 선택 기준을 정리한다.
- GitHub Fork로 협업하기원본 저장소에 쓰기 권한이 없어도 포크한 내 저장소에서 작업하고 풀 리퀘스트로 기여하는 오픈 소스 협업 방식을 다룬다. upstream 등록과 동기화, 공유 저장소 방식과의 비교까지 정리한다.
- GitHub 저장소 미러링모든 브랜치와 태그를 다른 원격 저장소에 그대로 복제하는 미러링을 다룬다. git clone --mirror와 push --mirror 절차, 동기화 자동화, LFS와 메타데이터 관련 주의사항을 정리한다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기