이번 글은, 저희 팀 프론트엔드 엔지니어 분께서 작성해주신 글입니다.
👉 원본 링크: https://rdo.sh/52hPhem
빠르게 만드는 것. 지난 3년 동안 Riido 프론트엔드 팀이 가장 자신 있어 하던 일이었습니다. 기획이 나오면 누구보다 먼저 화면을 띄웠고, 필요한 기능은 그때그때 붙여 나갔습니다.
AI가 코드를 쓰기 시작하면서 이 장점이 흐려졌습니다. 빨리 만드는 일은 이제 누구나 할 수 있게 됐고, 오히려 빠르게 쌓아 온 코드가 저희 발목을 잡고 있었습니다.
올여름 저희 프론트엔드 개발자 세 명은 9주 동안 Riido 프론트엔드를 처음부터 다시 만들었습니다. 이 글에서는 왜 다시 만들어야 했는지, 그리고 적은 인원으로 어떻게 그 일을 해냈는지 이야기해 보려고 합니다.
1/ Next.js를 쓰면서 Next.js의 장점은 쓰지 못했습니다
v1 Riido는 Next.js 14 위에서 돌아갔습니다. 돌아보면 문제는 대부분 기술 쪽에 몰려 있었습니다.
우선 느렸습니다. 로컬에서 코드를 고치면 화면에 반영되기까지 한참을 기다려야 했고, 배포는 더 오래 걸렸습니다. 올해 1월부터 7월까지 v1의 프로덕션 배포 88건을 보면 빌드에만 중앙값 8분 29초가 걸렸고, 13분을 넘긴 적도 있습니다. 문구 하나를 고쳐도 사용자에게 닿기까지 8분 넘게 기다려야 했습니다.
더 큰 문제는 저희에게 Next.js를 쓸 이유가 별로 없었다는 점입니다. Next.js의 강점은 서버에서 페이지를 미리 그려 보내는 서버사이드 렌더링(SSR)입니다. 검색에 노출돼야 하거나 로그인 없이 보는 페이지가 많을수록 효과가 큽니다. Riido는 로그인한 팀이 쓰는 B2B 협업 도구라 미리 그려 둘 정적인 페이지가 거의 없었고, v1의 실제 구조도 SSR과는 거리가 멀었습니다.
모든 페이지를 감싸는 AppContainer는 본문 전체를 ClientOnlyPersistGate로 감싸고 있었습니다. 이 컴포넌트는 서버에서 렌더링하지 않도록(ssr: false) 설정돼 있어서 서버가 보내는 HTML에는 본문이 비어 있었고, 브라우저가 자바스크립트를 실행해 저장해 둔 상태를 복원한 뒤에야 본문이 나타났습니다. 데이터도 마찬가지였습니다. 작업 목록 같은 핵심 도메인의 데이터는 브라우저가 실행된 뒤 React Query로 API 서버에서 가져왔고, 서버에서 데이터를 미리 받아 두는 페이지는 97개 가운데 2개뿐이었습니다. 'use client'를 선언한 파일이 521개였던 것도 그래서입니다. Next.js를 쓰고 있었지만 사실상 클라이언트 앱이었던 셈입니다.
기술 부채도 계속 쌓였습니다. 빠르게 기능을 더하는 팀이다 보니 정리는 늘 다음으로 밀렸습니다. 서버 통신이 대표적이었습니다. 뷰가 하나 생길 때마다 그 뷰를 위한 REST 엔드포인트도 하나씩 늘었습니다. 필터별 개수를 세는 API가 백로그용, 대기용으로 따로 있는 식이었습니다. 그렇게 v1 클라이언트에는 API 함수 557개, 서로 다른 REST 경로 381개가 쌓였습니다.
화면 코드도 마찬가지였습니다. 필터 드롭다운은 항목마다 해당하는 작업 개수를 보여 주는데, 이 개수를 계산하는 코드가 모든 뷰가 함께 쓰는 1,013줄짜리 파일 하나에 모여 있었습니다. 지금 화면이 리스트인지, 칸반인지, 캘린더인지를 URL로 확인하는 분기만 56곳이었습니다. 올해 4월, 타임라인에서 '보관된 작업 보기'를 켜도 필터 개수가 바뀌지 않는 문제를 고칠 때도 그랬습니다. 타임라인 화면 안에만 있던 토글 상태를 전역 스토어로 옮기고, 공용 파일의 담당자와 프로젝트 개수 계산에 '타임라인일 때만' 동작하는 분기를 각각 넣어야 했습니다. 다음 날에는 같은 처리를 캘린더에 붙였다가 그날 바로 되돌렸습니다. 기능 하나를 고치려 해도 다른 뷰가 같은 코드를 지나가는지부터 확인해야 했습니다.
AI를 본격적으로 쓰기 시작하면서 문제가 하나 더 드러났습니다. 빠르게 쌓아 온 구조는 AI에게도 일하기 좋은 환경이 아니었습니다. 조금씩 고쳐 나가는 방법도 생각했지만, 국제화만 해도 화면 파일의 절반 이상에 들어 있는 한국어 문구를 모두 꺼내야 했고, GraphQL 전환은 서버 통신 코드를 통째로 바꾸는 일이었습니다. 얽힌 코드를 고쳐 가며 옮기기보다 새 구조 위에서 다시 만드는 편이 빠르다고 판단했고, 결국 저희는 다 같이 처음부터 다시 만들기로 했습니다. 다만 주어진 조건은 예전 방식이라면 엄두를 내기 어려운 수준이었습니다.
- 기간은 9주
- v1 기능의 99%를 그대로 구현
- 성능 개선
- 국제화(i18n) 적용
- 개발 환경 개선
2/ Next.js에서 Vite와 React Router로, REST에서 GraphQL로
가장 먼저 기반을 바꿨습니다. Next.js를 걷어 내고 Vite와 React Router로 옮겼습니다. Riido는 별도의 API 서버와 통신하고, 상태와 화면은 브라우저에서 관리합니다. 서버에서 그려 보낼 화면이 없는 이런 구조에는 서버와 클라이언트를 함께 다루는 프레임워크보다, 브라우저 앱을 빠르게 빌드하는 Vite와 단순하고 예측하기 쉬운 클라이언트 라우터가 더 잘 맞는다고 판단했습니다.
Vite로 옮기고 나서 바로 체감된 건 개발 서버였습니다. 코드를 저장하면 기다릴 틈 없이 화면이 바뀌었고, 서버를 띄우는 시간도 짧아졌습니다. 빌드 결과물이 정적 파일이다 보니 배포도 단순해졌습니다.
뷰마다 늘어나던 REST 엔드포인트는 GraphQL 중심으로 정리했습니다. 타입은 GraphQL 스키마에서 자동으로 생성합니다. 다만 생성된 타입을 UI에서 바로 쓰지 않고, 매퍼 에서 프론트엔드 모델로 한 번 바꿔서 씁니다. 서버 응답이 바뀌어도 고칠 곳이 매퍼 한 군데로 모이게 하려는 선택입니다.
주요 스택은 이렇게 바뀌었습니다.
3/ 파일을 찾는 건 이제 사람이 아니라 AI입니다
AI에게 코드를 맡기다 보니 전제 하나가 바뀌었습니다. 이제 코드베이스를 뒤져 파일을 찾고 여는 쪽은 사람이 아니라 AI라는 점입니다. 그래서 아키텍처도 사람보다 AI가 잘 활용할 수 있도록 설계했습니다.
무엇을 해야 할지는 AI 에이전트가 코드를 다루는 방식에서 찾았습니다. Codex나 Claude Code 같은 에이전트는 코드베이스 전체를 한 번에 읽지 않습니다. 코드베이스를 읽을 때 폴더 구조와 이름 규칙이 에이전트에게 중요한 신호가 됩니다. 한 번에 읽는 양이 많을수록 정확도가 떨어진다는 점도 여러 연구에서 확인됐습니다.
도메인으로 나누고, 사용자 목적으로 한 번 더 나눴습니다. 제품 코드는 src/app/domains/<도메인> 아래에 두고, 그 안을 조회(Query)와 변경(Command)처럼 사용자가 하려는 일 단위로 쪼갰습니다. 도메인 밖에서는 각 도메인의 index.ts만 가져올 수 있고, 의존 방향은 정적 검사로 강제합니다.
제품 코드 파일은 300줄을 넘기지 않기로 했습니다. 주석과 빈 줄까지 포함한 기준입니다. 파일 수가 늘더라도 나누는 쪽을 택했습니다. 지금 도메인 코드 파일은 2,588개이고 중앙값은 79줄입니다.
규칙은 문서로 남기고, 검사는 자동으로 돌립니다. 에이전트가 작업을 시작하면 가장 먼저 읽는 AGENTS.md에 작업 원칙을 적어 두었습니다. pnpm run check 한 번이면 형식, 린트, 아키텍처, 번역 키, 테스트, 타입까지 29단계 검사가 돌고, 에이전트는 작업을 넘기기 전에 이 검사를 스스로 통과시킵니다. 사람에게는 깐깐해 보이는 규칙이 에이전트와 일할 때는 속도가 됩니다.
💡 AI에게 설명을 길게 늘어놓는 것보다, AI가 헤매지 않을 구조를 먼저 만드는 편이 빨랐습니다.
4/ 세 명이 같은 결과를 내도록: MBSW와 두 개의 루프
구조를 정하고 나니 다음 문제가 보였습니다. 3년 동안 쌓인 프론트엔드를 세 명이 나눠 다시 만들어야 했습니다. 사람마다, 그리고 각자 돌리는 에이전트마다 일하는 방식과 결과물이 다르면 9주 안에 끝내는 건 불가능하다고 봤습니다. 모두가 같은 기준으로 같은 결과를 내게 하는 장치가 필요했습니다.
첫 번째 장치는 디자인 시스템 MBSW입니다. 다시 만들 페이지만 97개였으니, 같은 디자인을 수없이 가져다 써야 했습니다. 예전 같으면 디자인 시스템을 만드는 것 자체가 큰 프로젝트였겠지만, AI와 함께라면 훨씬 수월하게 만들 수 있었습니다. MBSW는 디자인 원본을 관리하는 저장소와 이를 React 컴포넌트와 디자인 토큰으로 구현하는 저장소로 나뉘고, 결과물은 npm 패키지(@mbsw/ui, @mbsw/tokens)로 배포돼 Riido가 가져다 씁니다.
두 번째는 루프입니다. 여러 사람과 여러 에이전트가 동시에 일해도 부딪히지 않도록 작업마다 worktree를 따로 두고, 코드의 책임을 나누고, 공통 규칙과 자동 검사를 거치게 하는 작업 흐름을 만들었습니다.
- 메멘토(Memento)는 v1의 동작을 v2 구조와 GraphQL 계약 위에 다시 세우는 루프입니다. 조사한 v1의 동작과 내린 결정, 검증 결과와 다음 작업 지점을 기록으로 남깁니다. 그래서 어느 worktree의 어떤 에이전트든 같은 근거에서 작업을 이어받을 수 있습니다.
- 인셉션(Inception)은 Figma 디자인을 코드로 옮기는 루프입니다. 디자인을 고정한 뒤 작게 구현하고 화면을 비교하는 일을 반복하고, 디자인과 일치한다는 증거가 나와야 완료로 봅니다.
디자인 시스템과 두 루프는 여기서는 짧게만 소개했습니다. 어떻게 설계하고 운영했는지, 기술적인 이야기는 다른 글에서 더 자세히 다루겠습니다.
5/ 9주 뒤, 달라진 것들
v2는 계획한 9주 안에 배포됐습니다. 달라진 점은 사용자가 느끼는 것과 개발팀 안에서 느끼는 것으로 나눠 볼 수 있습니다.
사용자 쪽에서 가장 먼저 느껴지는 건 페이지 이동 속도입니다. 처음 여는 화면이 거의 다 그려지기까지 걸리는 시간은 중앙값 1.37초에서 0.78초로, 한 번 열었던 화면으로 돌아갈 때는 0.46초에서 0.29초로 줄었습니다.
이 수치는 9월 29일, 운영 중인 v1과 v2를 같은 노트북과 네트워크, 같은 계정과 팀으로 오가며 쟀습니다. 사이드바 메뉴를 누른 순간부터 이동한 화면에 새로 생기는 요소의 90%가 그려질 때까지의 시간입니다. 처음 여는 화면은 리스트 화면을 새로 불러온 뒤 칸반, 캘린더, 타임라인, 백로그, 대시보드, 스프린트에 처음 들어갈 때(화면 6개, 2회씩)이고, 다시 여는 화면은 이미 열어 본 화면 7개를 같은 세션에서 세 번씩 오갈 때입니다. 마우스를 올리면 미리 불러오는 v2의 기능은 빼고 클릭만으로 쟀습니다.
영어를 지원하게 되면서 한국어를 쓰지 않는 팀도 Riido를 쓸 수 있게 됐습니다.
개발팀 안에서는 로컬 개발이 가장 크게 달라졌습니다. v1에서는 개발 서버를 켜고 화면을 처음 열 때마다 컴파일을 기다렸고, 코드를 고친 뒤 화면에 반영되기까지도 한참 걸렸습니다. 하루에도 수십 번 반복하는 일이라 개발 속도를 떨어뜨리는 가장 큰 병목 중 하나였는데, v2에서는 이 기다림이 거의 사라졌습니다.
배포도 빨라졌습니다. Vercel 프로덕션 빌드 시간의 중앙값은 v1(올해 1~7월, 88건) 8분 29초에서 v2(9월, 40건) 2분 27초로 줄었습니다. 문구 하나를 고치고 8분 넘게 기다리던 일은 이제 없습니다.
빌드 시간은 Vercel 배포 기록에서 가져왔습니다.
페이지 이동은 9월 29일에 같은 디바이스와 계정으로 v1과 v2를 번갈아 열며 측정했습니다. 사이드바에서 칸반, 캘린더, 타임라인 같은 메뉴를 누른 뒤 화면이 거의 다 그려질 때까지 걸린 시간입니다. 페이지를 새로 불러온 뒤 처음 들어가는 경우와 이미 열어 본 화면으로 돌아가는 경우를 나눠 여러 번 측정하고 중앙값을 냈습니다.
무엇보다 AI가 일할 수 있는 환경이 생겼습니다. 디자인 시스템이 있어 화면을 만들 기준이 분명하고, 루프가 작업 순서와 검증을 잡아 주고, 로컬 개발 서버가 빨라 결과를 바로 확인할 수 있습니다.
팀도 달라졌습니다. 디자인 시스템과 루프를 직접 설계하면서 AI를 어떻게 써야 하는지에 대한 지식과 노하우가 눈에 띄게 늘었습니다.
마치며
재구축을 시작하기 전까지만 해도 저희에게 빠르다는 건 손이 빠르다는 뜻이었습니다. 지금은 AI가 헤매지 않고 일할 수 있는 구조를 얼마나 잘 만들어 두었는지가 속도를 정한다고 생각합니다. 빠른 팀이라는 말의 뜻은 달라졌지만, 저희는 앞으로도 빠른 팀이고 싶습니다.
Riido 1.0 때부터 화면의 사용성과 디테일은 중요한 과제였기 때문에 많은 리소스를 투입했고, 그만큼 여러 사람의 작업을 하나의 결과로 만들기 위해 협업 방식에 대한 고민도 많았습니다.
지금은 여기에 각자가 함께 일하는 AI까지 더해졌습니다. 저도 개발자였기에, 여러 사람이 만든 코드를 리뷰하고 합치는 데 얼마나 많은 노력이 필요한지 알고 있습니다.
지난 9주 동안, 이 복잡함을 사람이 아니라 구조로 풀어내기 위해 정말 다양한 방식을 시도하고, 배우고, 개선하는 모습을 바로 옆에서 지켜봤습니다. 정답이 정해져 있지 않은 상황에서 저희만의 방식을 찾아가는 과정이 정말 대단하게 느껴졌습니다.