Railway의 대규모 프론트엔드 마이그레이션: Next.js에서 Vite + TanStack Router로
Victor Ramirez | 2026년 4월 3일
Railway의 프로덕션 프론트엔드 전체가 더 이상 Next.js 위에서 돌아가지 않습니다. 대시보드, 캔버스, railway.com – 모든 게 이제 Vite + TanStack Router로 움직이고 있어요. 단 2개의 PR로 마이그레이션을 끝냈고, 다운타임은 제로였습니다.
Next.js는 좋았다. 그다음부턴 아니었다
Next.js는 Railway를 영점에서 월간 수백만 사용자를 서빙하는 프로덕션 앱으로 만들어주었습니다. 정말 훌륭한 프레임워크죠. 하지만 어느 순간 우리 제품에는 맞지 않는 도구가 되어버렸어요.
빌드 시간이 10분을 넘어섰거든요. 이중 6분이 Next.js 자체 때문이고, 그중 절반은 "페이지 최적화 마무리(finalizing page optimization)" 단계에서 멈춰있었습니다. 하루에 여러 번 배포하는 팀 입장에선 이 정도의 빌드 시간은 단순 불편함이 아닙니다. 매번 반복할 때마다 상당한 시간 비용을 지불하는 셈이죠.
Railway의 앱은 거의 전부가 클라이언트 사이드입니다. 대시보드는 풍부한 상태를 가진 인터페이스고, 캔버스는 실시간으로 동작합니다. WebSocket이 곳곳에 있어요. 그런데 Next.js의 서버 우선(server-first) 패러다임은 우리가 쓰지 않는 부분이었습니다. Pages Router 위에 우리만의 추상화 레이어를 덧붙여서 레이아웃과 라우팅 문제를 해결해야 했거든요.
Pages Router에 머물러 있으니 공유 레이아웃이 항상 거슬렸습니다. 모든 레이아웃 패턴이 그때그때 임시방편이었지, 프레임워크가 기본으로 지원하는 기능이 아니었던 거죠. App Router로 옮기면 이런 문제가 해결되겠지만, 그건 서버 우선 패턴에 더 무겁게 기울어져 있습니다. 우리 제품은 의도적으로 클라이언트 중심으로 설계되어 있으니, 그걸 도입하려면 필요하지도 않은 패러다임으로 전체를 다시 짜야 할 판이었어요.
왜 TanStack Start + Vite인가
우린 실제로 우리가 만드는 방식에 맞는 스택을 원했습니다. 명확하고, 클라이언트 우선이고, 빠른 반복이 가능한 것 말이에요. 게다가 이 스택으로 개발하는 게 정말 즐겁습니다. 제품 팀이 원했던 몇 가지 특징들이 우리를 결정적으로 설득했어요.
타입 안전 라우팅이 기본으로 제공됩니다. 라우트 파라미터와 검색 파라미터가 자동 추론되고, 자동완성이 전체 라우트 트리에서 작동합니다. 라우트 자체도 파일 시스템에서 생성되죠.
레이아웃이 일급 기능입니다. 경로 없는 레이아웃 라우트로 모든 임시방편이 대체되었고, 이제 조합 가능하고 예측 가능한 형태가 되었습니다.
개발 루프가 빨라서 생각할 필요가 없을 수준입니다. HMR이 즉시 반영되고, 시작 시간은 거의 무시할 수준이에요. 코드를 변경하고 결과를 보는 사이의 피드백 사이클이 사실상 사라집니다.
정말 필요한 부분만 SSR합니다. 마케팅 페이지, 변경 로그, 채용 공고처럼요. 나머지는 순수 클라이언트 사이드인데, 서버 렌더링의 이점이 없는 화면에 굳이 강제할 필요가 없거든요. (편집자 주: 아이러니하게도 이 블로그 자체는 아직 TanStack으로 옮겨지지 않았네요.)
명시적인 모델입니다. 즉, TanStack은 프레임워크의 "마법"을 덜 사용하고 내부 동작을 더 많이 제어할 수 있게 설계되어 있습니다.
우리 팀의 몇몇이 휴가 때 TanStack Start를 시도해봤는데 반응이 일치했습니다. 이걸로 개발하는 게 좋고, Railway의 대시보드 같은 제품에는 벤치마크만큼 중요합니다.
2개 PR, 다운타임 제로
선택을 내린 후 본격적으로 시작했습니다. 머지 전 스쿼시하기 전엔 아마 수백 개의 커밋을 만들었을 겁니다.
수백만 사용자를 서빙하는 프로덕션 프론트엔드를 200개 이상의 라우트로 마이그레이션하는 건 보통 수개월에 걸쳐 병렬 운영하고 점진적으로 전환하는 작업입니다. 우린 일정이 빠듯했으니 2개의 PR로 끝내버렸습니다.
첫 번째 PR은 모든 Next.js 특화 기능을 제거했습니다. next/image, next/head, next/router 같은 것들 말이에요. 각각을 네이티브 브라우저 API나 프레임워크 무관한 대체재로 바꿨습니다. 이 PR은 프레임워크 자체를 바꾼 게 아닙니다. 단지 모든 Next.js 의존성을 제거해서 두 번째 PR이 깔끔한 프레임워크 교체가 되도록 한 거죠.
두 번째 PR은 프레임워크 자체를 바꿨습니다. 200개 이상의 라우트를 마이그레이션했어요. 라우팅과 무관한 모든 것을 페이지 파일에서 개별 React 컴포넌트로 먼저 추출한 다음, 원래 페이지 트리에서 모든 라우트를 생성했습니다.
그 다음 서버 레이어로 Nitro를 추가했고, next.config.js를 Nitro 설정으로 대체했습니다. 500개 이상의 리다이렉트, 보안 헤더, 캐싱 규칙이 한곳에 통합되었어요. Next.js가 제공하던 폴리필(Buffer, url.parse 등)을 브라우저 네이티브 대체재로 바꿨는데, 덤으로 코드도 더 깔끔해졌습니다.
일요일 아침 일찍 머지했습니다. 팀이 즉시 Discord 라이브 전쟁실에서 테스트를 시작했고, 수정 사항들이 그날 안에 계속 배포되었어요. 다운타임은 없었습니다.
우리가 포기한 것들
더 빠르고 명시적인 스택을 얻었지만, 대가는 있었습니다.
이미지 최적화 기능. next/image를 일반 <img> 태그와 Fastly의 엣지 이미지 최적화로 대체했습니다.
생태계의 일부. next-seo, next-sitemap 같은 도구들을 작은 인하우스 대체재로 만들었어요. 그리 복잡하지 않고 추가 의존성도 줄었습니다.
성숙도. TanStack Start는 새로운 도구라 거친 부분들이 있을 수 있습니다. 하지만 방향이 맞고, 메인테이너들이 반응이 빠르고, 우리가 Vite와 TanStack을 스폰서하고 있으니 괜찮습니다. 그들의 방향성을 믿거든요.
Railway의 프론트엔드는 Railway 위에서 실행됩니다
우린 우리의 프로덕션 프론트엔드를 사용자들과 같은 방식으로 운영합니다. PR마다 프리뷰 배포, 헬스 체크...