단일 도메인에서 여러 Next.js 호스팅 하기

Multiple Apps Same Domain
Micro-frontend

이 글은 2024-02-25에 작성되었어요.

프롤로그


굳이 단일 도메인으로 사용할 앱을 여러 개로 나누어 호스팅 하는 이유가 뭐지? 하는 의문이 생기실 수 있습니다.

이 글은 모종의 이유로 여러 앱을 이용해 단일 도메인으로 배포해야하는 사람들을 위해 내용을 정리해보았습니다.

참고

아래 내용은 Monorepo를 기반으로 작성되었습니다.

따라서 앱의 규모에 따라 레포가 무거워지는 이슈가 발생함에 따라 일부 도메인에서는 부적절 할 수 있습니다.

또한 빌드 타임 통합을 기반으로 합니다. 런타임 통합을 기대하신 분들에게는 안타깝지만 다음에 기회가 되면 포스팅하겠습니다. 🙂

사실 저 또한 마이크로 서비스를 구현하기 위해 Module-federation으로 런타임 통합을 구상 해봤는데요. 리서치와 테스트를 진행하면서 아쉽게도 현재 MF와 서버 사이드의 지원이 다소 불안정하다는 것으로 판단하였기 때문입니다.

관련 내용을 탐구하기 위해 테스트 하는 과정 중 버그로 인해 버전이 다운그레이드 되는 등 불안정한 모습을 많이 보였습니다.

Module-federation/nextjs-mf 의 ReadMe 내용 중 일부 발췌

긍정적인 점은 Module-federation의 메인테이너가 기술적인 지원을 위해 열심히 업데이트 중인 것으로 알고 있습니다.

MF에 대해서는 꾸준히 네트워크를 유지하면서 관련 내용을 수집하고 공부하고 있으니 차후 좋은 정보를 얻게되면 다시 공유하도록 하겠습니다.

앱을 분리하는 이유


우선 호스팅 방법을 알아보기에 앞서, 왜 그토록 사람들은 앱을 분리하려고 하는 걸까요?

필자도 주니어이기 때문에 정확하지 않을 순 있지만, 개인적으로 최근 앱들이 발전하면서 코드의 양이 많아지기 시작해서지 않을까 싶습니다. 규모가 큰 경우 페이지 개수가 경우에 따라 수 백개를 넘나들 수도 있죠. 그럼 내부적으로는 엄청난 양의 코드가 사용될테고 관리 부분에서 점차 한계에 부딪힐 수 있습니다.

한번 생각 해 볼까요?

수백 개의 페이지를 운용하는 규모가 큰 서비스에서 배포 전 테스트 코드를 통과시키도록 한다고 가정만 해보겠습니다. 단순히 배포 절차만 진행한다고 가정해도 이 모든 테스트 코드를 다 통과시킨 후에 빌드하고 배포하는 과정이 얼마나 오래 걸릴까요? 만약 중간에 실패하고 다시 시작하는 경우라면요?

상상만해도 개발 과정에 의미없이 소요되는 시간이 엄청 많아지겠네요.

이런 문제들이 생기면서 필요에 의해 분리하고 독립적으로 유지하려는 움직임이 생겼습니다.

앱을 분리하면 분리된 앱은 서로 의존적이지 않기 때문에 하나의 앱에서 문제가 생겨도 다른 앱에 영향이 가지 않습니다. 또 배포 과정이 앱 별로 독립적으로 진행되기 때문에 전보다 빠르고 안정성있게 배포 할 수 있게 되었어요. 필요에 따라 앱마다 오너쉽을 부여하여 자신의 서비스만 관리하여 서비스의 품질을 높일 수도 있습니다.

호기심이 생기실까요?

어떻게 하나요?


결론부터 말씀드리면 아래와 같습니다.

  1. 서버를 여러 개로 나누고, 각 서버 별로 매칭될 페이지를 정한다.
  2. 공통 디자인 시스템이나 유틸 등을 나눈다.
  3. 각 서버 별로 개별 배포하고 정한 도메인에 접속한 라우트를 기반으로 매칭되는 서버를 설정한다.

3단계로 요약 될 만큼 간단하죠?

그 이유는 사실 위 방법만 생각하면 ‘Multirepo’와 큰 차이가 없기 때문이에요.

멀티레포로 운용하면 당장 서버를 분리하는 요구사항을 만족하지만, 프론트엔드의 특성 상 공통된 디자인 시스템 등을 사용하는 데 불편함을 감수해야 합니다.

왜냐하면 공통된 디자인 시스템을 사용하기 위해서는 UI가 추가/수정 될 때마다 패키지를 배포해야 하는데요. 제가 직접 해본 적은 없지만 팀 내 관련 개발을 해보신 분의 입장에 따르면 굉장히 불편한 일이라고 합니다. 사실 단순히 생각만 해봐도 불편할거라는 느낌이 딱! 오더라고요.

따라서 저는 Monorepo(이하 모노레포)를 통해 하나의 레포에서 관리하는 것을 추천합니다. 자신이 사용하는 툴을 이용해 모노레포 환경을 구축해주세요.

필자는 여러가지 장, 단점을 판단해본 뒤 Turborepo를 사용해 환경을 구축했습니다. 모노레포 환경으로 구현하면 하나의 레포 안에 여러 개의 서버와 공통 패키지를 관리할 수 있습니다. 이러면 공통 패키지를 배포하지 않아도 빌드 타임에 사용할 수 있겠네요!

만족스럽습니다. 이제 본격적으로 서버와 도메인을 연결시켜 보겠습니다.

기본적인 원리는 다음과 같습니다. (멀티레포 운용 방식과 크게 다른 점은 없습니다.)

  1. 서버 별로 트래픽을 받기 위한 이름을 추가한다.

    ex) some.domain.kr/server-alpha 등

  2. Ingress를 통해 도메인 별로 매칭되는 서비스를 정한다.

    • Ingress란 쿠버네티스의 클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트.
    • 트래픽 라우팅은 인그레스 리소스에 의해 정의된 규칙에 의해 컨트롤 됩니다.

    참고 자료1

    ex) ‘/home’ 으로 접속 시 ‘server-alpha 서비스 라우팅’ 하도록 Ingress controller 설정

    혹시 자신이 Ingress 외에 다른 트래픽 라우팅 도구가 있다면 똑같은 방식으로 컨트롤 해주세요.

  3. 만약 server-alpha에 대한 서버로 트래픽이 요청오면 server-alpha는 자신만의 server-alpha라는 이름을 Chunk, 이미지 등에 대한 요청 경로 앞에 Prefix로 추가한다.

    ex) Chunk 요청 경로는 some-domain/server-alpha/next/chunk/{…} 으로 진행되어야 한다.

  4. MiddleWare를 통해 전달받은 요청경로를 내부 서버 경로로 변경해서 요청하도록 한다.

Next.js를 사용한다면 생각보다 간단합니다.

Next는 Prefix라는 옵션을 제공하는데요. Prefix에 자신이 원하는 이름을 입력하면 모든 요청 경로 앞에 추가해줍니다. 또한 Next Image 옵션도 Prefix를 붙일 수 있기 때문에 모든 요청 경로를 간단하게 수정할 수 있습니다.

단점은 없어요?


아쉽지만 개인적으로는 런타임 통합 방식이 마이크로 서비스에 가장 적합하다고 생각합니다.

그 이유는 결국 빌드 타임에 코드가 통합되어야 하기 때문에 완전히 앱별로 배포를 독립적으로 하기는 어렵거든요. 가령 공통 패키지가 모든 서버에서 사용되고, 이 공통 패키지가 업데이트 됐을 땐 모든 서버를 배포해야 . 물론 병렬로 빌드/배포를 진행하기 때문에 이전보다는 빠르겠습니다만,완벽한 대안이 되진 못했습니다.

이 밖에도 레포를 하나로 관리하면서 레포 자체가 무거워지는 것은 해결하지 못했습니다. Ingress controller의 Rule이 많이 늘어나는 것도 단점이겠습니다.

마무리


제가 첫 관련 내용을 탐구할 땐 어렵게만 느껴졌는데 정리하고 보니 생각보다 간단했었구나 싶기도 합니다. 별개로 글로 정리하고 설명하는 것은 더 쉽지 않아 잘 전달되었을 지 걱정도 됩니다.

혹시나 이해가 잘 안되는 부분이나 개선했으면 하는 부분이 있다면 언제든지 피드백 주세요.

감사합니다. 😄