안녕하세요.
오늘은 현대적인 웹 개발 환경에서 점점 더 중요해지고 있는 Next.js monorepo(turborepo) 환경 구축과 이를 Vercel에 안정적으로 배포하는 방법에 대해 깊이 있게 다루어보려 합니다.
하나의 서비스가 성장하다 보면 필연적으로 여러 개의 프로젝트를 관리해야 하는 시점이 옵니다. 사용자용 웹사이트, 관리자 페이지, 그리고 공통으로 사용되는 UI 컴포넌트 라이브러리까지. 이 모든 것을 별도의 레포지토리로 관리하다 보면 코드 중복과 의존성 관리라는 늪에 빠지기 쉬운데요. 저 역시 비슷한 고민을 하다가 Turborepo를 도입하며 많은 문제를 해결할 수 있었습니다. 그 과정에서 얻은 기술적 통찰과 배포 팁을 상세히 정리해 드립니다.
서비스 성장에 따른 고민: 왜 모노레포(Monorepo)인가?
처음 프로젝트를 시작할 때는 단일 레포지토리로 충분합니다. 하지만 서비스가 확장되어 별도의 'Admin' 페이지가 필요해지거나, 'Documentation' 사이트를 구축해야 할 때 우리는 고민에 빠집니다. "이미 만들어둔 유틸리티 함수와 UI 컴포넌트를 어떻게 재사용할 것인가?"라는 질문이죠.
복사해서 붙여넣는 방식은 당장은 빠를지 모르나, 나중에 버그가 발견되었을 때 모든 프로젝트를 일일이 수정해야 하는 재앙을 초래합니다. 이때 등장하는 개념이 바로 **모노레포(Monorepo)**입니다. 여러 개의 프로젝트(apps)와 공유 패키지(packages)를 하나의 레포지토리에서 관리하는 방식이죠.
많은 모노레포 도구 중에서도 특히 Next.js monorepo(turborepo) 조합이 각광받는 이유는 Vercel과의 완벽한 궁합 때문입니다. Turborepo는 구글과 페이스북 같은 빅테크 기업들이 사용하는 고성능 빌드 시스템의 장점을 가져오면서도, 설정은 매우 간결하게 유지해 줍니다. 특히 '캐싱' 기능은 빌드 시간을 혁신적으로 단축해 주어 개발자 경험(DX)을 크게 향상시킵니다.
배포 구조의 이해: 1 Repo, N Vercel Projects
모노레포를 처음 도입할 때 가장 많이 혼란스러워하는 부분 중 하나가 "레포지토리는 하나인데, Vercel에는 어떻게 나타날까?" 하는 점입니다. 결론부터 말씀드리면, Next.js monorepo(turborepo) 내의 app 수만큼 Vercel 프로젝트도 독립적으로 생성되고 관리되어야 합니다.
예를 들어, 여러분의 모노레포 안에 apps/web과 apps/admin이라는 두 개의 Next.js 프로젝트가 있다면, Vercel 대시보드에서도 두 개의 별도 프로젝트를 생성해야 합니다.
- 웹 서비스 프로젝트: 깃허브 레포지토리를 연결하고 루트 디렉토리를
apps/web으로 설정 - 어드민 서비스 프로젝트: 동일한 깃허브 레포지토리를 연결하되 루트 디렉토리를
apps/admin으로 설정
이 방식이 처음에는 번거롭게 느껴질 수 있습니다. "관리 포인트가 늘어나는 것 아닌가?"라는 걱정이 들 수도 있죠. 하지만 실제로 운영해 보면 각 앱마다 고유한 도메인을 할당하고, 환경 변수를 다르게 가져가며, 개별적으로 배포 로그를 확인할 수 있다는 점에서 매우 합리적인 방식임을 알게 됩니다. Vercel은 하나의 깃 레포지토리에서 발생하는 수많은 커밋 중, 해당 프로젝트와 관련된 변경 사항이 있을 때만 영리하게 배포를 수행하기 때문입니다.
Turborepo를 도입하며 느낀 초기 진입장벽과 해결책
처음 Turborepo를 접했을 때 가장 낯설었던 부분은 구조 설계였습니다. 일반적인 폴더 구조와는 사뭇 다르기 때문입니다. 하지만 기본 원리만 이해하면 금방 익숙해질 수 있습니다.
보통 다음과 같은 구조를 가집니다:
apps/: 실제 배포되는 Next.js 서비스들 (web, admin 등)packages/: 여러 앱에서 공용으로 사용하는 코드 (ui, eslint-config, typescript-config 등)turbo.json: 전체 파이프라인과 캐싱 정책 설정
실제로 제가 진행했던 프로젝트에서는 공통 UI 라이브러리를 수정했을 때, 해당 라이브러리를 참조하는 앱들만 선별적으로 빌드하고 테스트하도록 설정하는 과정이 매우 중요했습니다. Turborepo의 pipeline 설정을 통해 어떤 작업이 이전 결과물에 의존하는지 정의할 수 있는데, 이 부분이 처음에는 조금 복잡하게 느껴질 수 있습니다. 하지만 한 번 제대로 설정해두면 배포 속도가 비약적으로 빨라지는 것을 체감할 수 있습니다.
Vercel에서 Next.js 모노레포를 배포하는 구체적인 단계
이제 핵심인 Vercel에서의 배포 방식에 대해 알아보겠습니다. Vercel은 Turborepo를 만든 팀이 운영하는 플랫폼인 만큼, 모노레포 배포 과정이 매우 직관적입니다. 하지만 몇 가지 세부 설정이 꼬이면 배포 실패를 겪을 수 있습니다.
1. 프로젝트 가져오기 (Import Project)
Vercel 대시보드에서 'New Project'를 클릭하고 깃허브 레포지토리를 연결합니다. 앞서 언급했듯이, 배포하려는 앱의 개수만큼 이 과정을 반복하게 됩니다. 이때 가장 중요한 것은 Root Directory 설정입니다.
모노레포의 실제 루트가 아니라, 내가 배포하고자 하는 특정 앱의 경로(예: apps/web)를 선택해야 합니다. Vercel은 해당 폴더가 모노레포의 일부임을 자동으로 감지하고 "Turborepo 환경임을 감지했습니다"라는 메시지와 함께 최적의 설정을 제안해 줍니다.
2. 프레임워크 프리셋 및 빌드 명령 확인
Vercel은 보통 Next.js를 자동으로 감지합니다. 하지만 Next.js monorepo(turborepo) 환경에서는 빌드 명령을 신중하게 확인해야 합니다.
- Build Command: 기본적으로
cd ../.. && npx turbo run build --filter=web와 같은 형태가 됩니다. 여기서--filter옵션은 오직 해당 프로젝트와 그 의존성만을 빌드하도록 지정하는 아주 중요한 역할을 합니다. - Install Command: 모노레포에서는 루트에 있는
pnpm-lock.yaml이나yarn.lock을 참조해야 하므로, 이 부분도 Vercel이 자동으로 처리해 주지만 커스텀이 필요할 때가 있습니다.
3. 환경 변수(Environment Variables) 설정
각 앱마다 필요한 환경 변수가 다를 수 있습니다. Vercel 프로젝트 설정에서 해당 앱에 맞는 변수들을 등록해 줍니다. 공유 패키지에서 사용하는 환경 변수가 있다면, 이를 사용하는 모든 앱 프로젝트에 동일하게 등록해 주어야 빌드 타임에 에러가 발생하지 않습니다.
성능 최적화: 빌드 시간을 줄여주는 Remote Caching 설정
Turborepo의 진정한 가치는 캐싱에 있습니다. 내 로컬 컴퓨터에서 한 번 빌드한 내용은 다시 빌드할 필요가 없죠. 그런데 문제는 협업 상황입니다. 동료 개발자가 빌드한 결과물을 내가 공유받을 수 있다면 얼마나 좋을까요?
이것을 가능하게 하는 것이 Vercel의 Remote Caching입니다. Vercel에 배포를 연결하면 별다른 설정 없이도 원격 캐시가 활성화됩니다. npx turbo login과 npx turbo link 명령어를 통해 로컬 개발 환경과 Vercel 서버의 캐시를 동기화할 수 있습니다.
실제로 제가 운영하던 프로젝트에서 전체 빌드 시간이 10분에서 2분 내외로 단축되는 놀라운 경험을 했습니다. 수정되지 않은 패키지는 캐시된 결과물을 그대로 사용하기 때문입니다. 이는 단순한 속도 개선을 넘어, 전체적인 개발 생산성과 CI/CD 비용 절감으로 이어집니다.
실무에서 겪은 이슈와 배포 시 주의사항
완벽해 보이는 **Next.js monorepo(turborepo)**와 Vercel 배포 방식에도 주의할 점은 있습니다.
첫 번째는 의존성 호이스팅(Hoisting) 문제입니다. pnpm을 사용할 때 가끔 특정 패키지를 찾지 못하는 경우가 발생하곤 합니다. 이럴 때는 .npmrc 파일에 public-hoist-pattern[]=* 설정을 추가하거나, 의존성 선언이 누락되지 않았는지 꼼꼼히 살펴야 합니다.
두 번째는 Ignored Build Step 설정입니다. 모노레포 특성상 특정 앱과 전혀 상관없는 폴더(예: apps/docs)의 코드만 수정되었음에도 불구하고 apps/web이 다시 빌드되는 경우가 있습니다. Vercel 프로젝트 설정의 'Git Settings' 탭에서 npx turbo-ignore 명령어를 활용하면, 정말로 변경 사항이 있는 경우에만 빌드를 트리거하도록 최적화할 수 있습니다. 이 과정이 빠지면 불필요한 빌드 리소스 낭비가 심해질 수 있으니 꼭 체크하시기 바랍니다.
마지막으로, Vercel 프로젝트 개수의 증가에 따른 관리 전략입니다. 앱이 5개, 10개로 늘어나면 대시보드가 복잡해질 수 있습니다. 이때는 프로젝트 명명 규칙(Naming Convention)을 정하는 것이 좋습니다. 예를 들어 [서비스명]-[환경]-[앱이름] (예: myproject-prod-web) 형태로 이름을 지으면 나중에 검색하고 관리하기가 훨씬 수월해집니다.
마무리하며: 기술적 성장을 위한 선택
지금까지 Next.js와 Turborepo를 활용해 모노레포를 구축하고 Vercel에 스마트하게 배포하는 과정을 살펴보았습니다. 처음에는 설정 파일 하나하나가 낯설고 앱마다 Vercel 프로젝트를 새로 만들어야 한다는 점이 번거롭게 느껴질 수 있지만, 프로젝트가 커질수록 이 구조가 주는 안정감과 편리함은 대체 불가능합니다.
코드의 재사용성을 높이고, 빌드 속도를 최적화하며, 일관된 개발 환경을 유지하는 것. 이것이 바로 우리가 모노레포를 지향해야 하는 이유입니다. 특히 Vercel이라는 강력한 플랫폼을 만났을 때, **Next.js monorepo(turborepo)**의 잠재력은 극대화됩니다. 여러분의 프로젝트에도 이러한 현대적인 워크플로우를 도입해 보시는 것을 적극 추천드립니다.
이번 포스팅을 마치며 느낀 점 (회고)
기술 블로그를 작성하면서 저 역시 과거에 겪었던 시행착오들을 다시금 복기할 수 있었습니다. 처음 모노레포를 구성하고 Vercel 프로젝트를 하나하나 연결할 때, "이게 맞는 방향일까?"라고 스스로 질문했던 기억이 납니다. 하지만 시간이 지나 여러 개의 서비스를 동시에 운영하게 되었을 때, 당시 구축해둔 모노레포 구조 덕분에 공통 라이브러리 수정 한 번으로 모든 서비스의 버그를 잡을 수 있었던 쾌감은 잊을 수 없습니다.
특히 이번 포스팅을 통해 "앱의 수만큼 배포 프로젝트가 늘어난다"는 핵심적인 운영 포인트를 짚어드리고 싶었습니다. 많은 분들이 이 지점에서 '모노레포인데 왜 관리가 분산되는 느낌이지?'라고 오해하시곤 하거든요. 하지만 그것은 분산이 아니라 **'독립적인 배포 전략의 공존'**이라는 점을 이해하는 것이 중요합니다.
긴 글 읽어주셔서 감사합니다.