프론트엔드 개발자
신혼부부의 장기 재무 설계를 자동화하는 AI 모바일 금융 서비스입니다. 일반적으로 재무 설계는 전문가 상담이 필요한 복잡하고 진입장벽이 높은 영역이지만, Finberry는 챗봇과의 자연스러운 대화 형태로 이를 풀어냈습니다.
사용자가 챗봇 온보딩을 통해 소득·자산·부채·소비 패턴 등을 입력하면, AI가 결혼·출산·내집마련·은퇴 등 생애주기 이벤트에 맞춰 맞춤형 재무 로드맵을 생성합니다. 실시간 스트리밍 응답과 강력한 데이터 검증을 통해 복잡한 금융 도메인을 누구나 사용할 수 있는 인터페이스로 구현하는 것이 핵심 목표였습니다.
Finberry의 유료 AI 재무 컨설팅 상품을 판매하기 위해 결제 시스템을 연동해야 했습니다. 상품 선택부터 결제 완료까지 주문 → 결제로 이어지는 흐름을 구성하고, 각 단계마다 화면과 데이터가 올바르게 갱신되도록 관리해야 했습니다. 특히 결제를 마친 뒤 화면을 이동하거나 뒤로가기를 누르면 결제 승인 화면으로 다시 돌아가는 문제가 있어, 결제 이후의 화면 흐름을 정리해야 했습니다. 또한 런칭 전 "심사 제출 후 환불 기능이 반드시 있어야 한다"는 요구사항이 추가되어, 환불 플로우까지 함께 구현해야 했습니다.
Toss Payments SDK를 연동해 사용자가 선택한 상품을 주문으로 확정하고, 해당 주문을 결제 단계로 넘기는 흐름을 구현했습니다. 결제 성공 페이지에서는 서버에 결제 승인을 요청해 결과를 확인한 뒤 완료 화면을 보여주고, 실패하면 장바구니로 돌려보냈습니다. 결제 이후 이동은 브라우저 기록을 대체하는 방식으로 처리해, 뒤로가기를 눌러도 결제 승인 화면으로 다시 돌아가지 않도록 수정했습니다. 또한 추가된 환불 요구사항에 맞춰 백엔드 팀과 협의해 환불 API를 연동하고, 사용자가 환불을 요청할 수 있는 UI를 함께 구현했습니다. 결제 금액 검증 같은 민감한 로직은 클라이언트가 아닌 서버에서 처리하도록 역할을 명확히 나눴습니다.
결제와 환불 플로우가 안정적으로 동작했고, 결제를 마친 사용자가 승인 화면으로 되돌아가 혼란을 겪는 문제도 사라졌습니다. 추가된 환불 기능까지 반영해 예정된 일정에 서비스를 오픈할 수 있었습니다.
SDK를 붙이는 작업 자체보다 결제 이후 화면 이동처럼 예상하지 못한 흐름을 어떻게 정리할지가 훨씬 더 어려운 문제였다는 것을 느꼈습니다. 또한 결제 플로우가 완성된 뒤 환불 기능이 추가로 필요해졌을 때, 기존 결제 플로우와 충돌 없이 반영하려면 백엔드 팀과의 협업과 책임 분리가 필수라는 것을 체감했습니다. 또한, 금전이 오가는 기능은 프론트엔드와 백엔드가 각자 어디까지 책임질지 명확히 나누는 것이 중요하다는 점을 다시 한번 느끼게 되었습니다.
Finberry의 AI 챗봇은 백엔드에서 SSE로 답변을 토큰 단위로 보내주는 구조였습니다. 받은 토큰을 그대로 화면에 붙이면 글자가 불규칙하게 몰려서 표시되어 읽기 어려웠고, 답변이 스트리밍되는 도중 사용자가 추가로 질문을 입력하면 대화 순서가 꼬일 수 있는 문제가 있었습니다. 또한 재무 리포트에는 DSR, 순자산처럼 일반 사용자에게 낯선 금융 용어가 많아 리포트를 읽다가 막히는 지점이 생겼습니다.
팀원이 연결한 SSE 스트림 위에서 챗봇 응답 화면을 구현했습니다. 받은 토큰을 마지막 답변 메시지에 누적해 렌더링하고, 토큰마다 짧은 딜레이를 두어 타이핑하듯 자연스럽게 표시되도록 했으며, 답변을 기다리는 동안에는 로딩 표시를 보여줬습니다. 답변이 스트리밍되는 동안에는 입력창과 전송 버튼을 비활성화해 대화 순서가 꼬이지 않게 했고, 개인 재무 챗봇의 메시지 상태를 별도 Recoil atom으로 분리해 다른 챗봇과 메시지가 섞이지 않도록 했습니다. 리포트의 금융 용어에는 하이라이트를 적용하고, 용어를 누르면 챗봇에 "○○이 뭔가요?" 질문과 설명이 자동으로 추가되도록 용어 사전과 연동했습니다.
긴 답변도 끊김 없이 자연스럽게 읽히는 챗봇 화면이 되었고, 답변 도중 중복 입력으로 대화가 꼬이는 문제가 사라졌습니다. 사용자는 리포트를 읽다가 모르는 용어를 바로 눌러 설명을 확인할 수 있어, 리포트와 챗봇이 하나의 흐름으로 이어지게 되었습니다.
같은 스트리밍 데이터라도 화면에 어떤 속도와 단위로 보여주느냐에 따라 사용자가 느끼는 응답의 자연스러움이 크게 달라진다는 것을 배웠습니다. 또한 실시간으로 바뀌는 화면에서는 기능을 추가하는 것만큼, 사용자가 예상하지 못한 시점에 입력하는 경우처럼 상태가 꼬일 수 있는 지점을 먼저 막아 두는 것이 중요하다는 점을 체감했습니다.
Finberry는 사용자가 입력한 소득·자산·부채·지출 데이터를 바탕으로 소득, 부채, 아파트 DSR·LTV, 자녀·자동차 등 목표 자금, 투자, 은퇴까지 30여 종의 재무 리포트를 보여주는 서비스입니다. 리포트마다 데이터의 성격(비율, 목표 달성 여부, 연령별 추이, 종합 점수)이 달라 하나의 차트 형태로는 표현할 수 없었고, 특히 모바일 화면에서 수십 년에 걸친 자산 추이 같은 긴 시계열 데이터를 읽기 쉽게 보여줘야 했습니다. 숫자와 문장만으로 결과를 보여주면 사용자가 자신의 재무 상태와 목표 달성 가능성을 한눈에 파악하기 어려웠기 때문에, 핵심 지표를 직관적으로 읽히는 시각 요소로 바꾸는 것이 과제였습니다.
Chart.js(react-chartjs-2) 기반으로 도넛·파이·라인·바·레이더 차트 공통 컴포넌트 10개를 만들고, 리포트별 데이터 성격에 맞춰 조합했습니다. 목표 자금 리포트에는 성공·실패 비율을 중앙에 강조하고 선택한 항목을 돌출시키는 도넛 차트를, 재무 점수에는 레이더 차트를 적용했습니다. 연령별 자산 추이 라인 차트에는 Chart.js 커스텀 플러그인을 직접 작성해 주요 시점에 세로선과 말풍선 라벨을 캔버스에 그리고, 가로 방향 핀치 줌·패닝으로 모바일에서도 긴 기간을 탐색할 수 있게 했습니다. 금액은 만·억 단위의 한국식 표기로 포맷했고, 입력 데이터가 없는 사용자에게는 빈 화면 대신 별도의 안내형 차트를 보여주도록 처리했습니다.
30여 종의 리포트가 공통 차트 컴포넌트를 재사용해 일관된 시각 언어로 구성됐습니다. 새 리포트를 추가할 때도 데이터 형태에 맞는 공통 차트를 골라 붙이면 되는 구조가 되어 리포트 개발 속도가 빨라졌고, 차트별 스타일·포맷을 화면마다 따로 맞출 필요가 없어졌습니다. 사용자는 소득·부채·은퇴 같은 복잡한 재무 정보를 숫자 나열이 아닌 비율과 추이로 파악할 수 있게 되었습니다.
같은 데이터라도 어떤 차트로 보여주느냐에 따라 사용자가 받아들이는 메시지가 크게 달라진다는 것을 체감했습니다. 특히 모바일의 좁은 화면에서는 모든 정보를 한 번에 보여주기보다, 핵심 수치 하나를 강조하고 세부 내용은 줌이나 선택으로 탐색하게 하는 편이 이해하기 쉽다는 점을 배웠습니다. 금융 데이터는 화려한 표현보다 숫자 표기와 단위를 정확하게 맞추는 것이 더 중요하다는 점도 다시 한번 느끼게 되었습니다.
Finberry는 신혼부부를 타겟으로 출시한 AI 재무 상담 서비스였습니다. 출시 초기 실제 사용자층과 결제까지 이어지는 흐름에 대한 정량적 근거가 부족해 다음 제품 방향성을 정하기 어려웠고, 팀 내부 가설(20~30대·PC 위주)과 실제 사용자 행동 간 괴리가 있을 가능성이 높았습니다. 이를 검증할 데이터 분석 인프라가 전무했던 상태였습니다.
GA4로 디바이스·연령대 등 사용자 분포를 확인하고, Mixpanel에는 메인 화면의 결제 버튼 클릭(고정·플로팅 버튼 구분)부터 결제 승인, 결제 완료까지 결제 단계별 이벤트를 설계해 단계별 전환율을 측정했습니다. 분석 결과는 팀에 수시로 보고했습니다.
60대 이상 사용자가 예상보다 큰 비중을 차지한다는 사실과, 전체 접속의 95%가 모바일에서 발생한다는 점을 발견했습니다. 이는 팀 내부 가설(20~30대·PC 위주)을 뒤집는 결과였고, 수시로 보고한 분석을 통해 팀이 추측이 아닌 실제 사용자 데이터를 기준으로 제품을 논의할 수 있는 근거를 마련했습니다.
"느낌"이 아니라 데이터로 의사결정하는 경험을 통해, 개발자도 데이터를 통해 제품 방향을 함께 고민할 수 있다는 점을 느끼게 되었습니다. 또한 객관적인 데이터를 근거로 가지고 있으니 제품 회의에서 막연한 느낌이 아니라 구체적인 숫자로 확신을 가지고 의견을 제시할 수 있었고, 팀 내에서 제 제안의 설득력이 훨씬 높아지는 것을 경험했습니다. 그리고 무엇을 측정할지 이벤트 설계 단계에서 먼저 정의해야 의미 있는 데이터가 쌓인다는 점을 배웠습니다.
Finberry의 재무 리포트는 사용자가 입력한 나이·연차·월소득·저축액 등의 정보를 바탕으로 만들어집니다. 빈 값이나 음수, 연차가 나이보다 큰 값, 저축액이 월소득보다 큰 값처럼 잘못된 입력이 그대로 넘어가면 리포트 결과가 왜곡될 수 있었습니다. 또한 필드마다 검사 코드를 따로 두면 규칙이 여러 곳에 흩어져, 입력 항목이 바뀔 때마다 놓치는 부분이 생기기 쉬웠습니다.
Zod 스키마에 필드별 타입·범위 규칙과 한국어 안내 메시지를 한곳에 정의하고, React Hook Form의 zodResolver로 입력 폼에 연결했습니다. 여러 필드를 함께 비교해야 하는 규칙은 superRefine으로 작성해, 연차가 나이보다 크거나 저축액이 월소득보다 크면 해당 입력칸에 바로 오류를 표시하도록 했습니다. 필드 간 비교 규칙을 뺀 기본 스키마는 따로 내보내 정보 수정 화면에서도 재사용했습니다.
잘못된 값을 프론트엔드 입력 단계에서 먼저 걸러내, 왜곡된 데이터가 리포트 생성 단계로 넘어가지 않게 되었습니다. 덕분에 백엔드나 AI 파트에서 입력 오류를 따로 처리하거나 수정할 필요가 없었고, 사용자는 어떤 값을 고쳐야 하는지 입력 화면에서 바로 알 수 있었습니다. 검증 규칙이 스키마 한 곳에 모여 있어 입력 항목이나 기준을 바꿀 때도 스키마만 수정하면 되었습니다.
입력값을 어느 단계에서 검증할지 정하는 것도 팀 간 역할 분담의 일부라는 점을 느끼게 되었습니다. 또한 검증 규칙을 코드 곳곳이 아닌 스키마 한 곳에 선언해 두면, 기획이 바뀌어도 변경 범위를 예측하기 쉬워진다는 장점을 체감했습니다.
Finberry는 스타트업 서비스로, 빠른 배포와 잦은 제품 업데이트를 통해 시장의 피드백을 신속하게 반영하는 것이 초기 성장의 핵심이었습니다. 그러나 AWS S3/CloudFront에 정적 프론트엔드를 배포하는 구조임에도 초기에는 로컬에서 빌드한 결과물을 수동으로 S3에 업로드하는 방식이었습니다. 빌드 환경이 개발자마다 달라 같은 코드에서 다른 결과물이 나오는 문제가 있었고, 배포 시간이 5~10분씩 소요되며 환경 변수 누락 같은 휴먼 에러가 잦았습니다.
GitHub Actions로 PR 머지 트리거 기반의 자동 배포 파이프라인을 구축했습니다. 환경별로 별도 워크플로우 (dev/production)를 작성하고, 각각 다른 환경 변수와 S3 버킷을 매핑했습니다. 특히 dev와 production 환경을 명확히 분리한 이유는, 개발 중인 코드가 곧바로 운영 환경에 올라가지 않도록 dev 환경에서 충분히 테스트하고 이슈를 수정한 뒤에만 production에 배포하는 안전장치를 두기 위함이었습니다. 이를 통해 스타트업 특유의 빠른 배포 속도를 유지하면서도 운영 안정성을 함께 확보할 수 있었습니다. 빌드 → S3 sync → CloudFront 캐시 무효화 순서로 구성해, 배포가 끝나면 사용자에게 바로 최신 버전이 반영되도록 했습니다.
배포가 코드 머지와 동시에 자동화되어 휴먼 에러가 사라졌고, 환경별로 안정적인 분리가 가능해졌습니다. 배포 시간도 일정하게 관리되어 긴급 수정이 필요한 상황에서도 안전하게 대응할 수 있게 됐습니다. 또한 production 배포 전 dev 환경에서 충분한 테스트 기간을 확보할 수 있게 되면서, 개발자·디자이너·기획자가 함께 실제 동작을 확인하며 의견을 맞추는 협업 시간이 자연스럽게 생겼고, 운영에 반영되기 전 이견을 조율하거나 UX를 다듬을 수 있게 됐습니다.
처음에는 CI/CD를 단순히 "배포를 자동화해 시간을 아끼는 도구"로 생각했지만, 실제로 구축해보니 CI/CD는 팀이 속도와 안정성을 동시에 챙기기 위한 장치에 가깝다는 것을 배웠습니다. 특히 dev/production을 분리하면서 생긴 테스트 기간에 개발자·디자이너·기획자가 같은 화면을 보며 의견을 맞출 수 있게 된 점이 기억에 남습니다. 기술적인 결정 하나가 팀의 일하는 방식까지 바꿀 수 있다는 점을 체감했고, 앞으로 인프라·파이프라인을 설계할 때도 코드 관점뿐 아니라 팀의 협업 흐름까지 함께 고려 해야 한다는 기준을 갖게 됐습니다.
CreditConnect는 이커머스 사업자를 위한 대출 중계 플랫폼 으로, 사용자 그룹에 따라 세 가지 오피스 구조로 설계되었습니다. FO(Front Office)는 쿠팡·네이버스토어 등을 운영하는 판매 사장님들이, BO(Back Office)는 회사 내부 운영 직원이, PO(Partner Office)는 금융사 직원이 사용합니다.
판매몰을 운영하는 셀러는 카드결제·할부 등의 이유로 판매금이 바로 지급되지 않고 정산까지 시간이 걸립니다. 고객이 '구매확정'을 하면 정산예정금이 잡히는데, CreditConnect는 이 정산예정금을 담보로 대출을 신청할 수 있도록 금융기관과 셀러를 연결해 줍니다. 일반적으로 금융기관이 증빙자료가 부족한 사업자에게 판매몰 자료를 일일이 요청하기 어렵다는 문제를 해결하기 위해, CreditConnect는 각 판매몰 어드민에 접속해 데이터를 자동 스크래핑하고 대출 시작부터 종료까지 매일 최신 자료를 금융기관에 제공하며 중간다리 역할을 수행합니다.
CreditConnect는 대출 중개 금융 서비스여서 보안상 로그인 토큰(JWT)의 유효 시간을 짧게 설정해야 했습니다. 문제는 사용자가 작업 중 토큰이 만료되면, 화면에서 동시에 호출되고 있던 여러 API가 한꺼번에 "인증 실패(401)"를 받아 화면이 깨지거나 세션이 갑자기 끊기는 일이 자주 발생했다는 점입니다. 단순히 "401이 오면 토큰을 새로 받자"고 처리하면 실패한 요청들이 각자 따로 토큰 갱신을 요청해, 한 번이면 될 작업이 여러 번 중복되고 서로 순서가 꼬여 세션이 완전히 끊기는 문제로 이어졌습니다.
Axios 인터셉터에 Request Queue 패턴(요청 대기열 방식)을 도입했습니다. 실패한 요청들을 각자 처리하지 않고 한 줄로 줄 세워서 처리하는 방식으로, 401 응답이 오면 가장 먼저 들어온 요청 하나만 토큰 갱신을 시도하고 나머지는 대기열(큐)에 잠시 보관합니다. 갱신이 성공하면 대기 중이던 요청들에 새 토큰을 붙여 자동으로 다시 시도하고, 실패하면 한꺼번에 에러로 처리합니다. 이 모든 과정을 공통 레이어에서 처리해, 각 페이지는 토큰 갱신을 전혀 신경 쓰지 않고 평소처럼 API만 호출하면 되도록 만들었습니다.
사용자는 토큰이 만료됐다는 사실조차 인지하지 못한 채 작업을 이어갈 수 있게 됐고, 금융 데이터 접근이 중단 없이 처리됐습니다. 중복 갱신 요청이 한 번으로 줄어들어 서버 부하도 감소했고, 401로 인한 화면 깨짐 이슈가 완전히 사라졌습니다.
인증 플로우에서 가장 위험한 건 단순한 코드 실수가 아니라 "동시에 여러 요청이 일어날 때 서로 꼬이는 상황"이라는 것을 직접 체감했습니다. 특히 금융 서비스에서는 세션이 끊기면 사용자 신뢰를 크게 잃기 때문에, "사용자가 전혀 눈치채지 못하는 자연스러운 처리"가 결국 가장 좋은 UX라는 것을 배웠습니다.
CreditConnect는 셀러의 매출 데이터와 대출 정보를 다루는 금융 서비스여서, 보안 심사 요건에 따라 짧은 시간 안에 반복되는 로그인 시도를 제한해야 했습니다. 동시에 정상 사용자가 비밀번호를 몇 번 잘못 입력했다고 바로 막히면 안 되고, 잠금이 걸렸을 때는 언제 다시 시도할 수 있는지 명확히 알려줘야 했습니다. 또한 공지사항·판매몰 연동 안내처럼 운영자가 입력한 HTML을 화면에 그대로 렌더링하는 영역이 있어, XSS 공격에 대한 방어도 필요했습니다.
로그인 실패 횟수와 잠금 시작 시각을 세션 스토리지에 기록해, 첫 실패 후 60초 안에 10회 연속 실패하면 5분간 로그인을 잠그도록 인증 훅(useAuth)의 에러 처리 흐름에 구현했습니다. 10회가 되기 전에 60초가 지나면 횟수를 초기화해 정상 사용자의 단순 오타는 잠금으로 이어지지 않게 했고, 이미 잠금이 진행 중일 때는 초기화하지 않아 잠금을 우회할 수 없도록 했습니다. 잠금 시에는 남은 시간을 초 단위로 보여주는 카운트다운 안내 모달을 띄우고, 시간이 끝나면 기록을 자동으로 정리했습니다. 운영자 입력 HTML을 렌더링하는 판매몰 연동 화면에는 팀 공통 유틸의 DOMPurify 기반 sanitize를 적용해 악성 스크립트가 실행되지 않도록 했습니다.
무차별 대입 형태의 반복 로그인 시도를 프론트엔드 단에서 1차로 차단하면서도, 정상 사용자는 잠금 사유와 남은 시간을 바로 확인할 수 있어 불필요한 문의 없이 다시 로그인할 수 있게 되었습니다. QA 과정에서 발견된 잠금 안내 모달의 본인인증 노출 오류도 함께 수정해 보안 요구사항을 일정 내 반영했습니다.
보안 기능은 "막는 것"만큼 "정상 사용자를 불편하게 하지 않는 것"이 중요하다는 점을 배웠습니다. 시간 창과 초기화 조건을 어떻게 두느냐에 따라 같은 잠금 정책도 사용자 경험이 크게 달라졌습니다. 또한 클라이언트에서 구현한 시도 제한은 사용자 경험과 1차 방어를 맡고, 서버는 최종적인 차단과 검증을 맡는 식으로 프론트엔드와 백엔드가 함께 대비해야 보안을 더 빈틈없이 챙길 수 있다는 점을 느끼게 되었습니다.
CreditConnect는 화면 단위(접근 가능 페이지), 메뉴 단위(보이는 메뉴), 요소 단위(버튼·필드 표시 여부)로 세밀한 접근 제어가 필요했습니다. 역할이 5개 이상이고 각 역할마다 권한 조합이 달랐으며, 권한 정책이 자주 변경되는 상황에서 코드 곳곳에 권한 체크 if문이 흩어지면 유지보수가 불가능했습니다. 또한 권한이 잘못 노출되면 금융사고로 직결되는 위험이 있었습니다.
권한 관리를 라우트·메뉴·요소의 3계층으로 분리했습니다. 라우트 레벨은 AuthRouteOutlet(로그인 필수 영역), ProtectedRoute(역할별 접근 제어), withoutAuth(비로그인 전용 영역)를 조합한 React Router v6 Outlet 기반 구조로 처리했습니다. 메뉴 레벨은 역할별 접근 가능 화면 리스트를 기반으로 사이드바를 동적으로 생성했고, 요소 레벨은 권한 훅으로 버튼·필드·액션 단위 가시성을 제어했습니다. 모든 권한 정책은 설정 파일 한 곳에 모아 관리해, 신규 권한 추가는 코드 변경 없이 설정만으로 가능하게 했습니다.
역할별 접근 제어가 안정적으로 동작했고, 권한 정책이 바뀔 때도 설정 파일 한 곳만 수정하면 되는 구조 덕분에 유지보수 비용이 크게 줄었습니다. 신규 역할이 추가되어도 기존 코드를 거의 건드리지 않고 확장 가능했고, 권한 체크 if문을 코드 곳곳에 뿌리지 않아도 되어 전체 코드의 가독성도 함께 향상됐습니다. 특히 "어떤 역할이 어떤 화면·메뉴·요소에 접근할 수 있는지"를 한 곳에서 명확하게 확인할 수 있어, 기획·운영 팀과 권한 정책을 논의할 때도 소통이 훨씬 수월해졌습니다.
권한 시스템은 초기에 구조를 얼마나 잘 잡느냐에 따라 이후 유지보수 난이도가 완전히 달라지는 영역이라는 것을 직접 체감했습니다. 권한 체크를 그때그때 코드 여기저기에 추가하면 단기적으로는 빠르지만, 시간이 지나면 if문이 곳곳에 뿌려져 어떤 역할이 어디에 접근 가능한지 아무도 확신하지 못하는 상황이 옵니다. 반대로 권한 정책을 설정 파일 한 곳에 모아두는 구조로 만들면, 새로운 요구사항이 들어와도 설정만 수정하면 되기 때문에 훨씬 안전하고 예측 가능합니다. 특히 금융 서비스처럼 권한 실수가 곧바로 사고로 이어질 수 있는 환경에서는, 누가 무엇에 접근 가능한지 한눈에 볼 수 있게 투명하게 관리하는 것이 중요하다는 점을 느끼게 되었습니다.
CreditConnect의 BO(Back Office)와 PO(Partner Office)는 관리자 도구 성격이 강한 포탈로, 30개 이상의 페이지가 대부분 "필터 조건 입력 → 검색 → 결과 테이블 → 행 선택 → 상세/수정"이라는 동일한 패턴을 반복하고 있었습니다. 페이지마다 비슷한 UI를 매번 새로 짜다 보니 일관성이 떨어지고 개발 시간도 누적되며 중복 코드가 많아졌고, 신규 페이지 추가에 평균 3~5일이 소요됐습니다.
먼저 Input, Table, Modal, Dropdown, DatePicker 등 50개 이상의 재사용 UI 컴포넌트를 직접 만들어 디자인 시스템으로 정리했습니다. 그리고 팀이 초기에 만든 config-driven SearchTable 공통 컴포넌트를 확장해 각 페이지에 적용했는데, 이는 페이지마다 검색·테이블·페이지네이션 로직을 매번 새로 코드로 작성하던 기존 방식 대신, 각 페이지에서는 "어떤 컬럼을, 어떤 필터로, 어떤 API로 불러올지"만 설정(config) 객체로 선언하면 나머지 동작(검색·페이지네이션·정렬·행 선택·CSV 내보내기)은 공통 컴포넌트가 자동으로 처리해주는 방식입니다. 즉, 페이지 개발자는 columns, filters, fetch 함수만 객체로 넘기면 전체 화면이 조립되는 구조로 만든 것입니다. 또한 페이지마다 달라지는 커스텀 동작은 render prop과 hooks를 통해 확장 가능하도록 열어두어, 공통화의 이점과 페이지별 유연성을 동시에 확보했습니다.
30개 이상 페이지에서 개발 시간이 평균 50% 단축됐고, UI 일관성이 자동으로 유지돼 디자인-개발 간 커뮤니케이션 비용도 줄었습니다. 신규 페이지 추가가 1~2일 단위로 가능해졌고, 기존 페이지 수정도 패턴 변경 한 번으로 일괄 적용 가능해 유지보수성이 크게 향상됐습니다.
관리자 도구처럼 비슷한 화면이 계속 반복되는 서비스에서는, 공통 구조를 한 번만 잘 만들어두면 개발 속도가 정말 크게 빨라진다는 것을 직접 경험했습니다. 다만 너무 많은 것을 공통 구조에 한꺼번에 집어넣으면, 나중에 특정 페이지에서만 다르게 동작해야 할 때 오히려 수정이 까다로워진다는 점도 함께 배웠습니다. 그래서 공통 구조는 최대한 단순하게 유지하되, 페이지마다 다르게 만들어야 하는 부분은 언제든 쉽게 바꿀 수 있도록 열어두는 균형이 중요하다는 것을 깨달았습니다.
CreditConnect 프로젝트에서 만 2년이 되기 조금 전, 팀에 새로운 프론트엔드 멤버가 합류하면서 팀이 2명에서 3명으로 확장되었습니다. 이 시점에 팀 내부에 코드 컨벤션과 개발 가이드 문서가 전혀 없다는 문제가 드러났습니다. 다른 사람이 작성한 코드를 이어서 작업할 때 새 기능을 바로 추가하지 못하고 리팩토링부터 진행해야 하는 경우가 많았고, 작성자마다 다른 스타일과 패턴이 섞여 있어 인수인계 비용이 크게 증가하던 상황이었습니다.
먼저 팀 전체가 참조할 수 있는 코드 컨벤션 및 개발 가이드 문서를 작성했습니다. 컴포넌트 구조, 네이밍, 상태 관리 패턴, API 호출 방식, 에러 처리 등 반복적으로 의사결정이 필요한 지점을 정리해 "이 프로젝트에서는 이렇게 한다"를 명시했습니다. 그러나 컨벤션 문서만으로 여전히 자잘한 오류들이 발생했고, QA 건이 지나치게 많다는 팀 내 의견을 수렴해 승인 1명을 필수로 하는 PR 코드 리뷰 프로세스 를 도입했습니다. 리뷰의 기준은 앞서 작성한 컨벤션 문서로 삼았고, 이로써 리뷰가 단순한 의견 교환이 아니라 공통 기준에 기반한 정렬이 되도록 설계했습니다.
컨벤션 문서화 이후 다른 사람 코드를 맡았을 때의 리팩토링 시간이 약 30% 절감됐습니다. 이어서 컨벤션 기반 코드 리뷰 프로세스를 도입한 뒤에는 릴리스 이후 버그가 약 40% 감소했습니다(QA 기간 중 발생한 이슈 카드 개수 기준). 팀 전체의 코드 이해도와 일관성이 향상되어 누구의 코드든 누구나 수정할 수 있는 구조가 만들어졌고, 신규 인원이 합류하더라도 컨벤션 문서를 기준으로 빠르게 온보딩할 수 있는 토대가 마련됐습니다.
처음에는 코드 리뷰 자체가 품질의 핵심이라고 생각했지만, 실제로는 "무엇을 기준으로 리뷰할 것인가"가 더 본질적인 문제였습니다. 컨벤션 문서 없이 리뷰를 시작했다면 리뷰어마다 다른 취향이 부딪치며 소모적인 논쟁으로 흘러갔을 것입니다. 팀 단위의 품질은 개인의 성실함이 아니라 공유된 기준에서 나오며, 기준을 먼저 만들고 그 위에 프로세스를 얹어야 지속 가능한 문화가 된다는 점을 배웠습니다.