아 그거 뭐였지

[Next.js] 남들 다하는 모노레포 나도 한번 말아보자 본문

Front-End

[Next.js] 남들 다하는 모노레포 나도 한번 말아보자

승발자 2025. 4. 14. 04:07
728x90
반응형

반갑다 여러분,

 

이번에 말아볼것은 "Monorepo" (이하 모노레포) 이다.

 

모노레포를 사용한지 얼마 안되기도했고, 그렇게 깊게 알지는 못해서 모노레포에 대해 글을 쓰는게 맞나 싶었지만,

 

죽었다 깨어나도 기존에 사용중이던 프로젝트를 모노레포로 구성하는 예시가 없었고,

(내가 찾았을땐 없었음 , 암튼 없었음) 

 

 

 

대부분 프로젝트를 처음부터 세팅하는 것을 기준으로 글이 작성 돼 있었다.

 

Shadcn/ui 공식 홈페이지 에도 초기 설정에 관련해서 얘기만 있고

기존의 멀티레포 구조에서 모노레포로 구성하는 방법에 대해서는 언급이 없는것을 알수있다.

 

그럼 Turborepo 공식 홈페이지에 가면 답이 있을까?

공식 홈페이지에 답이 있긴 하다. 하지만 백점짜리 답은 아니였다.

 

Add to an existing repository

기존 레포지토리에도 추가할수있어 해볼래?


라는 달콤한 타이틀로 모노레포 뉴비들을 유혹하고 있지만,

막상 들어가보면 설치 명령어 알려주고 실행 명령어 알려주고 끝난다.

 

지금 생각해보니 설치 명령어 알려주고 실행 명령어 알려주면 다 알려준 것 같기도 한데...

 

처음 세팅을 했었던 나는 도저히 천방지축 멀티레포를 모노레포로 합칠수가 없었다.

 

아니 정확히는 모노레포로 합치긴 했어도 그 이후 Shadcn/ui 를 붙이고 Tailwind를 설치하고 

 

확장하는 과정에서 어려움을 겪었었다.

 

 

그 고생 하면서 왜 씀?

기존의 프로젝트는 사용자 페이지와 어드민 페이지 각각 레포지토리를 가지는 멀티레포 구조였다.

 

레포지토리를 처음 만들때는

 

'유저와 어드민 페이지는 목적이 다르니깐 레포지토리를 따로 관리해야지 히히'

 

라는 생각과 함께 멀티레포로 가져갔지만, 

 

디자인만 보면 이게 어드민 페이지인지 유저 페이지인지 구별이 안갈정도로  차이점이 없었다.

 

 

어드민 페이지는 React 를 사용중이였고 유저 페이지는 Next.js 를 사용중이여서 기술 스택도 비슷했다.

 

이러다보니 어드민 페이지에서 hook을 만들어서 사용하다가

 

유저 페이지에서도 비슷한 기능이있어서 hook을 사용하려 했는데 알고보니 어드민 페이지에만 있어서

 

다시 유저 페이지에도 동일한 hook을 만든다던가,

 

디자인도 분명 어제 수정 했었던 컴포넌트같은데...? 보면 어드민 페이지나 유저 페이지 둘중 하나만 수정을 해서

 

동기화 시켜주는 작업을하며 같은 작업이 반복되는 피로감을 느끼게 되었다.

 

특히 반복 작업을 하면서 동일한 커밋을하고 PR을 올릴때 현타를 느끼게 되었고,

 

결국 모노레포로 전환을 결심하게 된다.

 

 

 

들어가기 앞서 Monorepo로 세팅하기 전 기술 스택은 다음과 같았다.

  어드민 유저
Core React + Vite Next.js
Styles Scss Scss
State Tanstack/query + Zustand Tanstack/query + Zustand
Package Manger yarn berry yarn berry

 

여기서 Shadcn/ui를 사용하기 위해 Scss를 Tailwind로 전환하는 작업을 하게 되니 많은 관심 부탁드린다.

 

시작

서론이 길었다. 그만큼 개고생 했다는걸 말해주고싶었다.

 

 

 

기존 멀티레포에서 모노레포로 전환하려면 우선 멀티레포의 최상단에 모노레포를 구성해야한다.

 

뭔소리임?

 

쉽게 말하자면 멀티레포를 하나의 레포지토리 안으로 밀어넣는다고 생각하면된다. (한곳에서 관리하기 위해)

 

방법은 두가지이다.

1. 모노레포 폴더를 하나 만들고 하위에 단순히 기존 레포지토리를 복사 / 붙여넣기

2. git subtree 를 사용해서 병합하기

 

여기서 첫번째 방법은 가오가 없으니 두번째 방법으로 진행해보자.

여담이지만 본인은 쫄아서가 아니라 빨리 세팅해보기 위해 첫번째 방법으로 진행했었다.

왜 뭐

 

Git Sub-tree 병합

1. turbo-repo 의 템플릿을 사용하여 모노레포를 생성한다.

// my-monorepo 는 폴더명이다 각자 원하는 폴더명으로 바꾸면된다.
pnpm dlx create-turbo@latest my-monorepo

// 프로젝트에 맞는 패키지 매니저의 명령어를 사용하면 된다.
yarn dlx create-turbo@latest 폴더명
npx create-turbo@latest 폴더명
bunx create-turbo@latest 폴더명

 

명령어를 입력하면 터미널에 어떤 패키지 매니저를 선택할건지 나온다.

본 게시글에서는 pnpm을 사용할것이기에 pnpm을 선택해준다.

pnpm을 선택 (키보드 화살표로 움직인다음 enter 클릭)

 

설치가 완료되면 아래와 같은 구조로 만들어진다.

my-monorepo/
├── apps/
│   ├── docs/      (Next.js 기본 예제)
│   └── web/       (Next.js 기본 예제)
├── packages/
│   ├── eslint-config/ (eslint 설정 루트 폴더)
│   ├── typescript-config/ (타입스크립트 설정 루트 폴더)
│   └── ui/        (공통 UI 패키지)
├── package.json
├── turbo.json
└── pnpm-workspace.yaml

 

apps/ 폴더밑에 예제들이 있고 우리는 apps/ 폴더밑으로 레포지토리를 밀어넣어 줄 것이다.

2. 작업을 위해 모노레포로 폴더로 이동 후 git init을 해준다.

# 새 모노레포 디렉토리 이동 및 Git 초기화
cd my-monorepo
git init -b main

 

3. 멀티레포로 구성되어있던 레포지토리를 sub-tree로 가져온다.

# 2. 첫 번째 레포(project-a) 가져오기
// project-a는 폴더이름이 될 예시 이름이다. 각 프로젝트명에 맞게 정해주면 된다.
// https://github.com/your-org/project-a.git 는 기존의 멀티레포로 구성되어있던 레포지토리의 remote url 이다

git remote add project-a https://github.com/your-org/project-a.git
git fetch project-a

# 3. HEAD 위치 수정을 위해 빈 커밋 추가 (처음에만 해주면됨)
git commit --allow-empty -m "Initial commit"

# 4. subtree로 병합
git subtree add --prefix=apps/project-a project-a main

 

4. 나머지 멀티레포도 동일한 명령어로 서브트리로 가져온다 ( Initial commit은 안해줘도됨 )

# 4. 두 번째 레포(project-b) 가져오기
git remote add project-b https://github.com/your-org/project-b.git
git fetch project-b

# 5. subtree로 병합
git subtree add --prefix=apps/project-b project-b main

 

 

위의 과정을 문제없이 진행했다면,

 

아래와 같이 폴더구조가 만들어지게 된다.

my-monorepo/
├── apps/
│   ├── docs/ ← 처음 만들때 있던 예제 폴더
│   ├── web/ ← 처음 만들때 있던 예제 폴더
│   ├── project-a/  ← 기존 소스코드
│   └── project-b/  ← 동일

 

사용하지않는 docs, web 폴더는 삭제해주자. ( 처음 만들어봐서 소중하다면 간직해봐도 괜찮다.)

마우스 우클릭 딸깍으로 삭제할 수 있지만, 개발자니까 터미널에 명령어로 삭제해보자

rm -rf apps/docs apps/web

 

괴상한 subtree 같은 걸 사용하고 git 병합하고 귀찮다고 느껴질 수 있다.


폴더를 복붙하는것과 어떤 장점이 있길래 이런 수고스러움을 했던것일까?

 

바로 기존의 레포지토리의 히스토리들을 그대로 가져올수있다

 

꽤 맛있는 작업이지 않은가? 당장 터미널에 git log 명령어를 쳐서 이전 기록들을 감상해보라.

 

여기까지 잘 따라 왔다면 멀티레포 구조를 모노레포 구조로 옮기는것에 성공했다고 볼수있다.

 

여기서 잠깐 subtree란?

더보기

subtree가 뭐임?

 

git subtree add, prefix 와 같이 평소 사용하던 명령어와는 달리 낯선 친구들이 많이 보인다.


하지만 알고 보면 꽤 괜찮은 녀석이다.


subtree는 다른 Git 레포지토리를 내 레포의 하위 폴더로 통째로 집어넣는 방법이다.


그것도 히스토리까지 몽땅 들고 온다.

예를 들어보자

지금 내가 만든 모노레포가 있고, 여기에 기존에 쓰던 project-a라는 레포를 집어넣고 싶다?

 
git subtree add --prefix=apps/project-a https://github.com/your-org/project-a.git main
 

요거 한 줄이면 apps/project-a 폴더가 생기고, 그 안에 기존 project-a 레포 전체가 들어온다.
심지어 git log를 쳐보면? 옛날 커밋들 그대로 다 살아있다. 전생의 기억을 갖고 환생한 레포라고 보면 된다.

출처 네이버 웹툰 https://comic.naver.com/webtoon/list?titleId=773796

그냥 복붙이랑 뭐가 다름?

좋은 질문이다.
project-a를 그냥 apps/ 폴더에 복사하면?

  • 코드만 들어온다.
  • 커밋 히스토리 없음. 과거 없음.

하지만 subtree를 쓰면?

  • 코드 존재
  • 과거 커밋 히스토리 존재

나중에 업데이트도 가능

subtree는 그냥 가져오기만 가능한 게 아니다.
나중에 원래 레포가 업데이트됐으면?

 
git subtree pull --prefix=apps/project-a https://github.com/your-org/project-a.git main

 

이렇게 하면 최신 커밋까지 싹 가져올 수 있다.
물론 merge 충돌이 날 수도 있지만... 각자 잘 해결 해보도록 하자...

 

웬만해서는 사용하지 않을듯하다.

힘들게 새집으로 이사왔는데 헌집으로 다시 돌아갈수는 없지않은가?

우린 두꺼비가 아니다.

결론

subtree는 레포를 폴더처럼 흡수하면서, 히스토리 까지 전부 가져오는 Git 기능이다.

 

5. turbo cli를 사용하기위해 turbo install

pnpm install turbo --global

 

 

해당 명령어로 설치하면 이제 turbo 명령어를 사용할수있게된다.

 

my-monorepo/package.json 파일의 script를 보면 이렇게 turbo 명령어가 적힌걸 볼수있다.

  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "format": "prettier --write \"**/*.{ts,tsx,md}\"",
    "check-types": "turbo run check-types"
  },

 

 

Monorepo 실행 시켜보기

설치도 완료된김에 my-monorepo 경로에서 터미널에 pnpm dev 를 입력해보면

 

짜자잔~

 

에러지롱

에러가 발생한다.

 

대부분의 프로젝트에서 node_modules 폴더는 gitignore 파일에 넣어서 git에 올리지 않다보니

subtree로 가져온 레포지토리에는 node_modules 폴더가 없을것이다.

 

이때 my-monorepo 경로에서 pnpm install 한번 딸깍 해주면 각 apps/ 폴더에있는 레포지토리에

package.json에 맞게 자동으로 설치가 되면서 node_modules가 만들어지게된다.

요로코롬 설치가 된다.

 

 

???: 서브 트리로 가져온 폴더마다 들어가서 각 패키지 매니저에 맞게 설치해줘야 되는거 아닌가요?

 

날카로운 질문이다. 어떻게 루트 폴더에서 딸깍 한번으로 설치가 알아서 되는걸까?

해답은 pnpm-workspace.yaml파일에 있다.

 

루트 폴더에있는 pnpm-workspace.yaml 파일을 살펴보면 현재는 별거 없을것이다.

packages:
  - "apps/*"
  - "packages/*"
단 세줄만이 적혀있을텐데, 여기서 app/* 라는 설정으로 인해 apps 하위에 있는 폴더들이 workspace라는 개념에 포함된다.
 
workspace는 모노레포 환경에서 굉장히 중요한 개념이다.
 
간단히 말해서, workspace는 “하나의 저장소 안에서 여러 프로젝트를 동시에 관리할 수 있게 해주는 설정” 이다.

 

apps 폴더 아래에 이것저것 넣어놨다고 해서, turbo가 자동으로 “젠장 난 너가 좋다 프로젝트” 하고 알아서 해주진 않는다.

대신 루트에 있는 pnpm-workspace.yaml 파일이 알려준다.

“apps, my-monorepo 딸이에요.”

 

 

이로인해 아래과 같은 기능들이 가능해진다.

  • pnpm install 딸깍으로 apps 폴더밑의 프로젝트 의존성 설치 완료
  • apps 폴더밑의 내부 패키지들끼리도 workspace: 로 의존성 주입가능
  • pnpm run dev, pnpm run build도 한번에 가능

현재는 폴더가 2개여서 그렇지만, 대형 프로젝트라면 훨씬 더 많은 프로젝트들로 쪼개져있을것이다.

그럴때마다 각 폴더에 들어가서 npm install 할 필요가없다. 

 

이게 모노레포의 첫번째 묘미라고 볼수있다.

 

이제 pnpm dev 명령어를 실행시키면

 

성공적으로 dev 환경으로 실행 되는것을 볼수있다.

화살표 위,아래 키를 사용해서 실행되어있는 환경들을볼수있다.

 

Shadcn/ui 설치는 다음 시간에

분명 다 한거같은 기분이였는데 사실 이제 시작이다.

 

생각보다 분량이 길어져서 원래 목표였던 Shadcn/ui를 설치는 다음 게시글에서 이어하도록 하겠다.

 

긴글 읽느라 고생했다. 다음 게시글에서 보자.

 

 

 

 

 

728x90
반응형
Comments