상태 관리의 미궁

52025년 08월 20일3

클라이언트에서 실행되는 코드의 양, 순차적인 데이터 요청, 그리고 하이드레이션 병목. 이 모든 문제의 저변에는 더 근본적이고 혼란스러운 현실이 자리 잡고 있었다. 바로 ‘상태(State)’를 다루는 방식이었다.

로렌 탄의 모니터에는 페이스북의 뉴스피드 컴포넌트 코드가 열려 있었다. 수백, 수천 줄에 달하는 코드는 그 자체로 하나의 거대한 미궁과도 같았다. 그 미궁의 입구와 출구, 그리고 모든 갈림길에는 어김없이 ‘상태 관리’라는 이름의 덫이 놓여 있었다.

처음 React가 훅(Hook)을 도입했을 때, useStateuseEffect는 개발자들에게 마법과도 같은 도구였다. 단 몇 줄의 코드로 컴포넌트에 동적인 생명력을 불어넣을 수 있었다. 하지만 애플리케이션이 복잡해지면서, 이 단순했던 마법은 점차 복잡한 저주로 변해가고 있었다.

문제는 상태가 한 종류가 아니라는 점에서 시작됐다.

화면의 다크 모드 여부(isDarkMode), 모달창의 열림 상태(isModalOpen)처럼 순수하게 클라이언트, 즉 사용자의 브라우저 안에서만 존재하고 관리되는 ‘클라이언트 상태’가 있었다. 이것은 다루기 비교적 간단했다.

하지만 진짜 문제는 ‘서버 상태’였다.

사용자 프로필, 게시물 목록, 친구 데이터처럼 그 원본이 서버의 데이터베이스에 존재하는 데이터들. 이것들은 네트워크를 통해 가져와야 했고, 언제든 서버에서 변경될 수 있었다. 즉, 클라이언트가 가진 데이터는 언제든 ‘오래된 것(stale)’이 될 수 있는 ‘사본’에 불과했다.

개발자들은 이 두 가지 종류의 상태를 같은 도구로 관리하려 애썼다. useEffect 안에서 fetch로 서버 데이터를 가져오고, useState를 세 개씩 선언해 data, isLoading, error 상태를 저장하는 패턴이 교과서처럼 번져나갔다.

여기에 더해, 여러 컴포넌트가 같은 데이터를 공유해야 할 때는 Redux, MobX, Zustand 같은 전역 상태 관리 라이브러리가 등장했다. 이 라이브러리들은 클라이언트 상태를 관리하는 데에는 훌륭했지만, 개발자들은 서버에서 가져온 데이터까지 이 거대한 클라이언트 저장소에 욱여넣기 시작했다.

그 결과는 참담했다. 클라이언트의 상태 저장소는 서버 데이터의 ‘캐시(cache)’ 역할까지 떠맡으며 비대해졌다. 데이터를 언제 다시 가져와야 하는지(refetching), 서버 데이터가 바뀌었을 때 어떻게 동기화해야 하는지(synchronization)에 대한 복잡한 로직을 개발자가 직접 구현해야만 했다. 코드는 거미줄처럼 얽혀 들어갔고, 데이터가 어디서 어떻게 흘러가는지 파악하기조차 어려워졌다.

로렌은 깊은 한숨을 내쉬었다.

“우리는 지금 두 개의 다른 문제를 하나의 어설픈 해결책으로 꿰맞추고 있는 거야.”

그녀의 눈에 비친 코드는 더 이상 로직이 아니었다. 그것은 React가 개발자들에게 떠넘긴 혼란의 증거였다.

‘데이터의 진짜 주인은 누구인가?’

이 근원적인 질문에 대한 답을 회피한 채, 클라이언트에서 모든 것을 해결하려는 시도가 이 모든 복잡성을 낳았다. 서버에 있는 데이터는 서버가 가장 잘 알고, 클라이언트의 상호작용은 클라이언트가 가장 잘 안다. 이 당연한 사실을 아키텍처 수준에서부터 인정해야 했다.

이 상태 관리의 미궁을 탈출하기 위해서는, 서버와 클라이언트의 역할을 명확히 나누고, 각자의 상태를 가장 자연스러운 방식으로 다룰 수 있는 새로운 길이 필요했다. 그 길의 끝이 어디로 향할지는 아직 아무도 몰랐지만, 더 이상 이 미궁 속에 머물러 있을 수만은 없다는 사실만큼은 분명했다.