Monorepo란? npm workspaces를 활용한 크로스 프로젝트 코드 공유 완벽 가이드
현대의 프론트엔드 및 풀스택 개발에서 제품군이 확장됨에 따라 우리는 종종 "여러 프로젝트에서 동일한 코드를 공유해야 하는" 상황에 직면합니다. 예를 들어, 일반 사용자를 위한 메인 웹사이트(Client App)와 내부 직원을 위한 관리자 패널(Admin Panel)이 있다고 가정해 봅시다. 이 둘은 독립적으로 실행되지만, 동일한 UI 컴포넌트 라이브러리, API 호출 로직 또는 타입(Type) 정의를 공유하는 경우가 많습니다.
두 프로젝트에 같은 코드를 복사하여 붙여넣는다면, 나중에 로직을 수정해야 할 때 개발자는 여러 프로젝트를 오가며 반복해서 수정해야 합니다. 이 과정에서 작업 누락이나 버전 불일치 같은 오류가 발생하기 쉽습니다. 이러한 문제를 해결하기 위해 Monorepo(모노레포) 아키텍처가 등장했으며, 현재 Node.js 생태계에서 npm workspaces는 가장 진입 장벽이 낮은 기본 도구 중 하나입니다.
Monorepo 아키텍처란?

Monorepo(Monolithic Repository)는 여러 다른 프로젝트(Projects)나 패키지(Packages)를 모두 하나의 거대한 Git 저장소(Repository) 내에서 통합 관리하는 것을 말합니다. 이와 반대되는 개념은 기존의 Polyrepo(Multi-repo)로, 각 프로젝트가 자신만의 독립적인 저장소를 가지는 형태입니다.
Monorepo의 핵심 장점
- 단일 진실 공급원 (Single Source of Truth): 모든 코드가 동일한 트리 구조 아래에 있으므로, 팀원들이 항상 일관된 코드베이스를 참조할 수 있도록 보장합니다.
- 손쉬운 코드 공유: 다른 프로젝트에 있는 공유 모듈을 참조할 때 패키지를 일일이 npm registry에 게시할 필요 없이 로컬에서 심볼릭 링크(Symlink)만으로 가능합니다.
- 일관된 의존성 관리: 서드파티 의존 패키지(예: React, Lodash)를 루트 디렉토리로 끌어올림(Hoist)으로써, 모든 하위 프로젝트가 완전히 동일한 버전의 패키지를 사용하도록 보장하여 버전 충돌을 방지하고 스토리지 공간을 절약할 수 있습니다.
- 대규모 리팩터링의 용이성: 공유 모듈의 API가 변경되었을 때, 해당 모듈에 의존하는 모든 프로젝트가 같은 저장소에 있기 때문에 개발자는 한 번에 변경 사항을 적용하고 TypeScript 컴파일러를 통해 잠재적인 오류를 모두 잡아낼 수 있습니다.
npm workspaces 이해하기
npm v7 버전부터 npm은 공식적으로 Workspaces에 대한 지원을 내장했습니다. 이는 같은 로컬 파일 시스템 내에 있는 여러 패키지의 의존성을 관리하기 위한 CLI 네이티브 기능을 제공합니다. 루트 디렉토리의 package.json만 적절히 설정해 주면, npm이 자동으로 하위 프로젝트의 의존성을 정리하고 서로 참조 할 수 있도록 만들어 줍니다.
실전 튜토리얼: npm workspaces를 활용해 코드 공유하기
지금부터 구체적인 예시를 통해 project-a, project-b 및 공유 모듈인 shared-utils를 포함하는 Monorepo를 구축하는 방법을 시연하겠습니다.
단계 1: 루트 디렉토리 생성 및 초기화
먼저, 우리 Monorepo의 루트 디렉토리가 될 새로운 폴더를 생성하고 프로젝트를 초기화합니다.
mkdir my-monorepo
cd my-monorepo
npm init -y
그런 다음, 생성된 루트 디렉토리의 package.json을 열어 수동으로 workspaces 필드를 추가합니다. 이는 이 Monorepo가 어느 디렉토리에 하위 프로젝트들을 포함하고 있는지를 선언하는 역할을 합니다. 일반적으로 프로젝트는 apps(애플리케이션)와 packages(공유 모듈)로 분류합니다.
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
주의: 루트 디렉토리의
package.json에는 반드시"private": true를 설정하여, 실수로 전체 워크스페이스가 공개된 npm 레지스트리에 배포되는 것을 방지해야 합니다.
단계 2: 공유 모듈(Shared Package) 생성
이제 공유할 로직을 담을 패키지를 만들어 보겠습니다. 루트 디렉토리 아래에 packages/shared-utils 폴더를 생성합니다.
mkdir -p packages/shared-utils
cd packages/shared-utils
npm init -y
packages/shared-utils/package.json을 편집합니다. 특히 "name" 필드에 주의하세요. 이 이름은 다른 프로젝트에서 이 모듈을 참조할 때 사용될 이름입니다.
{
"name": "@my-org/shared-utils",
"version": "1.0.0",
"main": "index.js"
}
이어서 해당 폴더 안에 index.js를 만들고, 공유하고자 하는 함수 코드를 작성합니다.
// 공유할 날짜 포맷 함수
function formatDate(date) {
return new Intl.DateTimeFormat('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(date);
}
// 공유할 덧셈 함수
function add(a, b) {
return a + b;
}
module.exports = {
formatDate,
add
};
단계 3: 두 개의 애플리케이션 프로젝트 생성
루트 디렉토리로 돌아와서, apps/ 디렉토리 아래에 두 개의 독립된 프로젝트를 생성합니다 (간단한 시연을 위해 기본적인 Node.js 프로젝트를 초기화하지만, 실무에서는 Next.js, React 또는 Express 프로젝트가 될 수 있습니다).
mkdir -p apps/project-a
mkdir -p apps/project-b
# project-a 초기화
cd apps/project-a
npm init -y
# project-b 초기화
cd ../project-b
npm init -y
각각 apps/project-a/package.json 및 apps/project-b/package.json의 "name"을 "project-a" 와 "project-b"로 변경해 줍니다.