단일 Rails 앱에서 Next.js로 마이그레이션

Hyeon·2025년 4월 21일
post-thumbnail

Ruby on Rails 기반의 단일 애플리케이션을 운영하던 시절이 있었다. 프론트와 백엔드가 한 덩어리였고, 단순한 UI 텍스트 수정을 하더라도 전체 앱을 빌드하고 배포해야 했다. API 테스트든 QA든, 무조건 전체 릴리즈 단위로 움직였기 때문에 작은 수정 하나에도 꽤 큰 비용이 들었다. 단순히 비효율적이라는 문제를 넘어서, 어느 시점부턴가는 실수를 방지하기 위한 방어적 개발이 당연시되기 시작했다.

그 와중에 프론트는 Angular도 아닌, deprecated된 AngularJS와 레일즈 템플릿(ERB)이 뒤섞여 있었다. 여기에 jQuery까지 혼용되고 있었고, 하나의 컨트롤러에 방대한 비즈니스 로직이 몰려 있는 구조였다. 타입스크립트는커녕 타입 추론조차 되지 않아 API 응답 하나 파악하는 데도 매번 콘솔로 찍어가며 확인해야 했다. 어디까지가 AngularJS고, 어디서부터 ERB인지도 명확하지 않았고, 페이지마다 구조와 렌더링 방식이 달라 기준조차 잡기 어려웠다. 지금 돌이켜보면 그야말로 혼돈 그 자체였다. 이런 상태로는 기능 개발도, 신규 인력 온보딩도 전혀 효율적일 수 없다고 판단했다.

프론트 분리의 필요성

가장 직접적인 계기는 반복되는 "전체 배포"였다. 프론트엔드 수정만 했을 뿐인데, 전체 API 테스트와 QA를 다시 수행해야 한다는 건 너무 비효율적이었다. 무엇보다 프론트엔드 개발자 입장에서 매번 Rails 전체 흐름을 의식하며 작업해야 하는 구조가 답답했다. 충돌도 자주 났고, 협업보다 충돌 해결에 더 많은 시간을 쓰는 느낌마저 들었다.

팀의 확장을 고려했을 때도 지금처럼 하나의 거대한 레포 안에 모든 코드가 섞여있는 구조는 한계가 분명했다. 프론트는 독립적으로 진화할 필요가 있었고, 마침 개인적으로도 Next.js를 꽤 오랫동안 도입하고 싶다는 생각을 해오고 있었다.

Next.js를 선택한 이유

SPA도 검토했지만, 서비스 특성상 SEO가 중요한 페이지가 분명 존재했고, CSR만으로는 초기 로딩 속도 이슈도 있었다. Next.js는 SSR/CSR/SSG를 상황에 따라 혼합해서 사용할 수 있다는 점이 결정적이었다. 무엇보다, React 기반이기 때문에 이후 프론트 개발자를 충원할 때도 학습 비용이 적을 것으로 판단했다.

페이지 단위 점진적 마이그레이션

처음부터 전체를 한 번에 옮기는 건 무리였다. 그래서 먼저 페이지별 트래픽과 복잡도를 기준으로 우선순위를 나눴고, Rails와의 의존성이 낮은 페이지부터 하나씩 Next.js로 이전해갔다. 이 과정에서 단순히 프레임워크만 교체하는 게 아니라, 구조 자체를 다시 설계할 필요를 느꼈다. 모든 코드는 작성되는 순간부터 레거시가 된다고 생각하고 있고, Next.js 역시 Rails가 Angaulr가 되었듯, 언젠가는 교체될 수 있다고 생각했다.

그래서 view(=Next.js)는 언제든 교체 가능하도록 최소한의 역할만 부여했고, 여기에 종속되지 않도록 view에 맞는 데이터를 가공하는 viewModel, 비즈니스 로직을 담당하는 useCase, 데이터 저장/조회 역할의 repository, 외부 API 호출을 담당하는 api 레이어로 나눴다. 클린 아키텍처에서 말하는 책임 분리와 방향성 흐름을 최대한 반영했다. 각 레이어는 교체 가능한 상태를 유지하면서, 내부 로직은 안정적으로 재사용할 수 있는 구조를 지향했다.

트래픽 분리 구조

Next.js와 기존 레일즈가 병행 운영되는 동안은 트래픽 분리도 중요했다. ALB(Application Load Balancer)에서 요청을 수신하고, 경로나 User-Agent를 기준으로 트래픽을 Target Group에 따라 분기했다. 각 그룹은 ECS에서 구동 중인 컨테이너에 연결되어 있었다. 이를 통해 하나의 도메인에서 두 서비스를 안정적으로 나눠 운영할 수 있었다.

AngularJS와의 공존

Next.js 전환 중에도 일부 페이지는 여전히 AngularJS로 운영되고 있다. 그런데 모든 페이지에 공통적으로 들어가는 컴포넌트는 중복 구현이 필요했다. 이걸 줄이기 위해 Next.js에서 만든 컴포넌트를 iframe으로 AngularJS 내에 삽입하고, postMessage를 통해 통신하는 구조로 처리했다.

API 타입 문제와 응답 구조 일관화

API 응답의 일관성이 부족했다. 같은 URI라도 호출 조건에 따라 응답 필드가 달라지는 경우가 많았고, 타입 정의는 커녕 주석조차 없는 상태였다. 이 정도면 프론트엔드 입장에선 그저 추측에 의존해서 코드를 짜라는 얘기였다. 결국 직접 Rails 백엔드 코드를 보며 응답 구조를 분석했고, 필요한 경우 v1 API를 레일즈에 직접 추가 구현했다. 프론트엔드에서는 타입스크립트 기반의 타입 정의를 정리하고, 안정성을 확보했다.

효과

가장 큰 변화는 개발 속도였다. 컴포넌트 단위 개발이 가능해지면서 신규 기능 도입 속도도 훨씬 빨라졌다. 이전에는 UI 하나 바꾸는 데도 수일이 걸렸다면, 이제는 몇 시간 내에 테스트 배포까지 가능해졌다. 온보딩도 수월했다. 명확한 폴더 구조와 타입스크립트 기반 덕분에 새로운 개발자들도 금방 익숙해질 수 있었다.

느낀점

기술 부채를 없애려면 무작정 새로운 기술을 쓰는 것보다, 그걸 어떤 구조로 받아들이고 팀이 어떻게 같이 움직일지를 고민하는 게 훨씬 중요하다는 걸 느꼈다. 이번 경험을 통해 나도 어떤 기술이든 도입할 때마다 "이게 나중에 또 부채가 되진 않을까?" 하고 생각하게 되었다. 그래서 구조를 짤 때도 최대한 느슨하게, 나중에 교체하더라도 부담이 덜 가도록 설계하려고 했다.

그리고 기술적으로 아무리 옳은 방향이라도, 팀이 함께 움직이지 않으면 그건 혼자만의 선택일 뿐이다. 결국 내가 만든 구조를 다른 사람이 같이 유지보수하게 될 거라면, 이해하고 납득한 상태에서 참여하게 만드는 게 훨씬 낫다.

profile
즐겁게하자

0개의 댓글