확장의 병목은 생각보다 작은 곳에 있다
'빠르게 확장한다'는 말을 들으면 대부분 라우팅 구조나 번들 전략을 떠올린다. 그런데 실제 프로덕션 경험을 들여다보면 이야기가 달라진다. dev.to에 공개된 freefixo.com 구축기와 velog의 dnd-kit 드래그 앤 드롭 구현기는 서로 다른 스케일의 문제를 다루고 있지만, 핵심 교훈은 놀랍도록 겹친다. 기술 선택의 트레이드오프는 항상 가장 작은 UI 디테일에서 먼저 터진다.
TanStack Router + Cloudflare Pages: 확장의 조건을 먼저 설계하라
125개 이상의 계산기를 혼자 구축한 경험은 '파일 하나 = 계산기 하나'라는 패턴이 얼마나 강력한지를 증명한다. TanStack Router의 파일 기반 라우팅은 새 도구를 추가할 때 기존 코드를 건드릴 필요가 없게 만든다. 타입 안전한 라우트 추론 덕분에 문자열 오타로 인한 런타임 버그도 사라진다. React Router 대신 TanStack Router를 선택한 이유가 바로 이 타입 안전성이었고, 검색 파라미터 핸들링의 학습 곡선이라는 트레이드오프를 감수할 만한 가치가 있었다고 저자는 평가한다.
Cloudflare Pages는 CSR SPA에 최적화된 선택이다. git push 한 번에 30초 안에 전 세계 CDN에 배포된다. 단, 함정이 하나 있다. 클라이언트 사이드 라우트에 직접 URL로 접근하면 기본적으로 404가 반환된다. _redirects 파일에 /* /index.html 200 한 줄을 추가해야 SPA 라우팅이 완성된다. 저자가 "창피할 만큼 오래 걸렸다"고 고백한 이 한 줄은, 인프라 선택이 UX에 직결되는 전형적인 사례다.
dnd-kit과 flex 레이아웃의 충돌: 라이브러리 예제를 맹신하지 말 것
dnd-kit 구현기는 더 흥미롭다. 공식 예제를 따라 DragOverlay를 적용했다가 두 개의 버그를 연속으로 마주쳤다. 첫 번째는 드래그 오버레이가 커서 위치와 어긋나는 문제, 두 번째는 드래그 중 폴더 이름이 글자 단위로 세로 분열되는 문제였다.
원인은 레이아웃 컨텍스트의 단절이었다. DragOverlay는 요소를 DOM 포탈로 꺼내 렌더링한다. 부모 flex 컨테이너의 align-items: stretch가 적용된 행은 포탈 밖으로 나오는 순간 그 컨텍스트를 잃는다. 폭 측정으로 보완을 시도했지만 타이밍에 따라 0이 잡히면서 내부 flex 자식들이 붕괴했다.
해결책은 놀랍도록 단순했다. DragOverlay를 제거하고 기본 in-place sortable로 전환하는 것이었다. useSortable이 반환하는 transform을 그대로 style에 적용하면, 요소는 포탈로 이탈하지 않고 리스트 안에서 커서를 따라 이동한다. 코드도 줄고 버그도 사라졌다.
이 경험이 주는 교훈은 명확하다. 라이브러리 예제를 따르기 전에 '내 레이아웃이 그 예제와 같은 전제를 갖는가'를 먼저 확인해야 한다. DragOverlay는 고정 폭 카드나 스크롤 컨테이너를 넘나드는 드래그에는 적합하지만, flex-stretch 기반 리스트 행에는 오히려 독이 된다.
접근성은 사후 조치가 아니라 설계 조건이다
dnd-kit을 선택한 이유 중 하나는 HTML5 드래그 앤 드롭 API 대신 포인터/키보드 이벤트 기반으로 동작한다는 점이다. KeyboardSensor를 함께 등록하면 키보드만으로도 리스트 순서를 변경할 수 있다. 드래그 핸들을 아이콘 버튼에만 분리한 설계(listeners와 attributes를 정렬 아이콘 버튼에만 부착)도 접근성과 직결된다. 행 전체가 아닌 핸들만 드래그 가능하게 만들면, 스크린 리더 사용자도 버튼의 역할을 명확하게 인식할 수 있다.
PointerSensor에 activationConstraint: { distance: 5 }를 설정한 것도 마이크로 인터랙션 관점에서 중요하다. 5px 이상 움직여야 드래그로 인식하게 함으로써 단순 클릭과 드래그를 구분한다. 이 한 줄이 없으면 버튼 탭이 의도치 않은 드래그로 오인되어 사용자 경험을 망친다.
'빠른 확장'과 '미완성 API' 사이의 현실적 설계
두 사례 모두 미완성 상태로의 출시를 선택했다. dnd-kit 구현기에서는 백엔드의 '폴더 순서 저장' 엔드포인트가 아직 없어서 저장이 클라이언트 화면에서만 유효하다. 새로고침하면 서버 순서로 돌아온다. 그러나 API가 준비되면 handleSaveReorder에서 mutation 호출 한 줄만 추가하면 된다는 점에서 확장 가능한 구조를 미리 잡아뒀다.
freefixo.com 구축기에서도 메타 태그와 검색 기능을 출시 이후에 붙였다고 솔직하게 고백한다. 40개가 준비됐을 때 이미 출시할 수 있었는데 늦췄다는 후회도 함께. '완벽하게 준비될 때까지 기다리지 말라'는 조언은 진부하게 들리지만, 두 사례 모두 그 가치를 실제 경험으로 증명하고 있다.
전망: 패턴의 가치는 다음 기능이 얼마나 쉬운가로 측정된다
두 사례가 공통으로 가리키는 방향은 하나다. 확장 가능한 패턴을 초기에 확립해두면, 이후 기능 추가는 그 패턴을 반복하는 것으로 족하다. 계산기 하나 추가가 파일 하나와 config 한 줄이고, 폴더 순서 저장이 mutation 한 줄이라는 것—이것이 현대적인 React 아키텍처가 지향하는 방향이다.
TanStack Router의 파일 기반 라우팅과 타입 추론, dnd-kit의 composable한 훅 설계는 모두 같은 철학을 공유한다. 도구는 복잡성을 숨기는 것이 아니라, 복잡성을 예측 가능한 패턴으로 관리하게 돕는 것이어야 한다. 그리고 그 패턴이 접근성과 마이크로 인터랙션까지 포괄할 때, 비로소 확장 속도와 경험 품질이 함께 올라간다.