최근 회사에서 팀원들과 프로젝트의 폴더구조에 대해서 이야기하는 미팅을 여러차례 가졌다. 중점적으로 논의한 것은 컴포넌트를 어떠한 기준으로 폴더링 할 것인가
였는데 프로젝트가 시간이 흘러 커지며 현재의 구조로는 여러가지 불편함을 갖게 되었기 때문이다.
pages 폴더 하위에 Component.tsx
파일이 존재하여 컴포넌트 이름으로 라우팅이 가능하다(?)
메인 프로젝트의 태생은 Next.js가 아니라 CRA 프로젝트였고 시간이 지나면서 Next.js로 마이그레이션을 진행했다. Next.js를 사용해 본 사람은 잘 알겠지만 파일시스템 기반의 라우팅을 지원해 pages 폴더 하위의 폴더 또는 파일 구조에 따라서 라우팅이 가능하다. 그런데 이 프로젝트의 태생이 Next.js가 아니었던지라 기존에 pages 폴더 하위에 해당 페이지에서 사용하는 컴포넌트의 파일도 함께 위치하고 있었고 이로 인해 컴포넌트 파일도 path가 되어 라우팅이 가능한 기묘한 현상이 나타나게 되었다.
기준없이 뒤섞이고 비대해진 Components 폴더
도메인 로직을 갖지 않은 공통 컴포넌트들은 root 폴더 바로 밑의 components 폴더에 모아두었다. 아토믹 디자인 패턴을 일부? 차용하여 Components 폴더 밑에서 Atoms과 그 외의 컴포넌트로 분류하여 담아두었는데, 이 방법도 썩 효과적이진 못했다. Atoms의 조건에 해당하는 컴포넌트들은 그리 많지 않았기 때문에 그 외의 컴포넌트들이 모여있는 부분은 기준없이 뒤섞이고 비대해 졌다.
따라서 이러한 문제점을 개선하기 위해 팀원 들과 폴더구조 개선 방향에 대해 논의 하기로 했다. 회의에 앞서 어떤 방법이 좋을지 고민하면서 다음과 같은 글을 찾아 읽고, 팀원들에게 공유했다.
공유한 아티클 중 특히 이 글을 참고하여 기능별 혹은 도메인 별로 폴더구조를 가져가는 방안에 대해서 많은 이야기를 나누었고 팀원들 다수가 기능별 폴더링의 장점에 대해서는 공감하지만 현재 프로젝트에 개선 방법으로 도입하기 에는 어려움이 있다는 결론에 다달았다.
우리가 어려움으로 꼽은 이유와 나눈 의견들은 다음과 같았다.
따라서 현재 프로젝트에 도입이 더 수월하고 직관적인 기준의 페이지 기반 컴포넌트 폴더링
을 도입 하기로 했다.
├── components // common한 컴포넌트
├── domainComponents
│ ├── contract
│ │ └── detail // 페이지
│ │ │ ├── Table
│ │ │ │ ├── Row
│ │ │ │ │ ├── Row.tsx
│ │ │ │ │ ├── Row.style.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Header
│ │ │ │ │ ├── Header.tsx
│ │ │ │ │ ├── Header.style.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── useTable.tsx
│ │ │ │ ├── Table.tsx
│ │ │ │ ├── Table.style.ts
│ │ │ │ └── index.ts
│ │ │ ├── DatePicker
│ │ │ │ ├── Hooks
│ │ │ │ │ ├── useDate.ts
│ │ │ │ │ └── useTime.ts
│ │ │ │ ├── DatePicker.tsx
│ │ │ │ ├── DatePicker.style.ts
│ │ │ │ └── index.ts
│ │ │ ├── detail.const.ts
│ │ │ ├── detail.utils.ts
│ │ │ ├── detail.type.ts
│
├── pages
│ ├── contract
│ └── detail
│
└── package.json
즉 domainComponents는 라우팅 구조에 따라서 폴더를 생성하고, 해당 페이지(폴더) 하위에 종속되는 컴포넌트들을 생성합니다.
Table 컴포넌트일 경우 Table 폴더를 만들고 그 밑에 Table.tsx 파일을 만들어 컴포넌트를 작성합니다. 스타일 파일은 Table.style.ts, 상수 파일은 Table.const.ts와 같이 작성합니다. 단 custom hook의 경우 use -.tsx 파일명으로 생성합니다.
custom hook의 파일명을 Table.hook.ts로 작성하지 않는 이유는 해당 컴포넌트에서 두 개의 custom hook을 사용하게 될 수 있기 때문입니다. 그리고 한 컴포넌트 내에서 두 개의 커스텀 훅을 사용할 경우, hooks 폴더를 생성하고 그 밑에 사용하는 여러 개의 커스텀훅을 넣어 놓습니다.
// Table 폴더의 index.ts 파일
export * from './Table';
예시로 contract/detail
페이지에서 사용되는 const
, type
, util
등이 있다면 하위 컴포넌트 폴더가 아니라 페이지 폴더 하위에 detail.const.ts
와 같이 파일을 만들어 작성합니다. 만약 두 개 이상의 페이지에서 사용되는 const
, type
, util
등이라면 root폴더 하위에 있는 공통의 constants
, types
, utils
폴더 밑에 파일을 생성하여 작성합니다.
폴더링 방법에 정답이야 없지만 그래도 일반적으로 통용되는 방식의 범주에 있는지가 궁금해졌다. 이 궁금증은 재밌게도 모노레포 이렇게 좋은데 왜 안써요?라는 의외의 글에서 해소 되었는데 해당 글에서는 pages-modules
라는 폴더 하위에 도메인 별로 분류하여 관리하는 것을 볼 수 있었다. components 뿐만 아니라 각종 모듈도 페이지 기준의 분류법을 적용한 것인데, 이 사례를 보면서 페이지 기반의 컴포넌트 폴더링이 나쁘지 않은 선택 이였다는 힌트를 얻을 수 있었고 나아가 컴포넌트 뿐만 아니라 다른 모듈들도 페이지 기준으로 분류하는 page-modules의 방법도 고려해 봄 직 하다는 생각이 들었다.
폴더구조는 프로젝트의 첫인상이다. 책으로 따지면 목차 같은 것인데, 이 목차가 잘 구성되어 있다면 한 눈에 책의 흐름이나 구조를 파악할 수 있다. 폴더구조도 마찬가지로 잘 정리되어 있다면 프로젝트의 아키텍처나 흐름을 단번에 파악할 수 있도록 해줄 것이며, 코드의 응집도를 높혀 수정이 용이하도록 할 것이다. 곱씹어 생각할 수록 참 기본적이지만 중요한 부분이라는 생각이 든다.
그렇다면 과연 좋은 폴더구조란 어떤 것일까? 정답은 없지만 이번에 고민해보며 몇 가지 중요하다고 생각한 점을 꼽아보자면 아래와 같다.
최근 폴더구조나 컴포넌트 구성에 대해 고민하다보면 프로젝트(서비스)는 끊임없이 변화하며 성장한다는 점에서 마치 유기체 같다는 생각을 하게된다. 곤충이 성장하기 위해 주기적으로 탈피를 하며 새로운 옷을 입 듯 프로젝트도 성장하며 그 규모와 특성에 따라 적절한 변화가 필요하다.
이 글에서 이야기 하듯 규모가 작은 프로젝트에서는 계층별 구조로도 충분할 수도 있고 성장해가면서 또 다른 구조가 필요할 수도 있다. 처음부터 프로젝트 구조를 잡을 때부터 대규모 프로젝트를 고려한 구조를 만들수도 있겠지만 어찌보면 그것이 오버엔지니어링일 수도 있는 것이고, 또 프로젝트 구조라는 것이 단순히 규모의 문제만은 아니기 때문에 참 어렵다. 🤔
프로젝트는 시간이 흐르며 다양한 변화를 맞이 하겠지만 폴더구조의 개선 혹은 변경은 비용이 상당히 많이 드는 일이라는 점에서 최대한 적은 변경으로 서비스를 확장해 나갈 수 있도록 미리 생각해보는 것이 필요하겠다. 그러니까 내가 고민해야 할 것은, 어떠한 프로젝트를 만났을 때 그 프로젝트의 규모나 특성 그리고 앞으로의 확장성을 고려하여 적절한 옷을 입힐 수 있도록 여러가지 구조를 고민해보고 Best practice를 찾는 것이 아닐까