로딩 UI의 품격

242025년 09월 08일3

Suspense가 데이터 로딩을 다루는 핵심 도구로 자리 잡으면서, 개발자들은 isLoading이라는 수동 상태 관리의 굴레에서 벗어날 수 있게 되었다. 하지만 Suspense가 가져다준 진짜 변화는 단순히 코드를 간결하게 만드는 것을 넘어, 로딩 상태를 보여주는 사용자 경험(UX) 자체를 한 단계 끌어올리는 데 있었다.

과거의 개발자들은 로딩 UI를 구현할 때마다 매번 같은 고민에 빠졌다.

“로딩 스피너는 어디에 위치시켜야 할까? 페이지 전체를 덮는 큰 스피너를 쓸까, 아니면 데이터가 들어갈 컴포넌트 영역에만 작은 스피너를 넣을까?”
“데이터 A와 데이터 B를 동시에 가져올 때, 스피너를 두 개 보여줘야 하나, 하나로 합쳐야 하나?”
“페이지 전환 시, 이전 페이지를 그대로 보여주면서 로딩을 표시할까, 아니면 일단 빈 화면으로 바꾼 뒤 스피너를 보여줄까?”

이 모든 결정은 개발자의 감에 의존했고, 그 결과 애플리케이션 곳곳의 로딩 경험은 일관성 없이 제각각이었다. 때로는 로딩 상태가 너무 짧게 깜빡이며 사용자를 더 혼란스럽게 만들기도 했다.

React Core Team은 이 문제를 해결하기 위해 Suspense에 아주 사려 깊은 기능을 설계했다. 바로 fallback UI의 렌더링을 React가 직접 제어하도록 만든 것이다.

<Suspense fallback={<Spinner />}>
  <UserProfile />
</Suspense>

여기서 Suspense의 역할은 단순히 UserProfile이 로딩 중일 때 Spinner를 보여주는 것에서 그치지 않았다.

로렌 탄은 Suspense의 동작 원리를 설명하는 내부 데모를 준비했다.

“만약 UserProfile의 데이터 로딩이 아주 빨리, 예를 들어 15밀리초 안에 끝난다고 가정해봅시다.”

그녀는 네트워크 속도를 조절하며 상황을 시연했다. 데이터가 거의 즉시 도착하자, 화면에는 Spinner가 전혀 나타나지 않았다. 곧바로 UserProfile 컴포넌트가 렌더링되었다.

Suspense는 기본적으로 약간의 ‘지연 시간(threshold)’을 가지고 있습니다. 로딩이 너무 빨리 끝나면, 스피너를 잠깐 보여줬다 감추는 것이 오히려 사용자 눈에 거슬리는 ‘깜빡임(flicker)’이 될 수 있죠. 그래서 React는 로딩이 일정 시간 이상 지속될 때만 fallback UI를 보여주도록 영리하게 동작합니다.”

팀원들의 얼굴에 감탄이 어렸다. 개발자가 setTimeout 같은 코드로 직접 구현해야 했던 미세한 UX 조정을, 이제 React가 프레임워크 차원에서 알아서 처리해 주는 것이다.

더 나아가, Suspense는 여러 개의 로딩 상태를 하나의 fallback으로 통합하는 것도 가능하게 했다.

<Suspense fallback={<PageSkeleton />}>
  <UserInfo />
  <FriendList />
</Suspense>

UserInfoFriendList가 각자 다른 API에서 데이터를 가져오더라도, 개발자는 더 이상 두 개의 isLoading 상태를 관리할 필요가 없었다. Suspense는 두 컴포넌트가 모두 준비될 때까지 PageSkeleton을 보여주다가, 모든 데이터가 도착하면 한꺼번에 실제 UI로 교체했다.

이것은 로딩 UI에 대한 패러다임의 전환이었다.

로딩 상태는 더 이상 개발자가 컴포넌트마다 신경 써야 할 ‘부가적인 처리’가 아니었다. 그것은 React 아키텍처의 일부가 되었고, 개발자는 그저 ‘무엇을 보여줄 것인가’라는 fallback만 선언하면 그만이었다. 나머지는 React가 알아서 가장 이상적인 타이밍에, 가장 부드러운 방식으로 처리해 주었다.

덕분에 애플리케이션의 모든 로딩 경험은 일관성을 갖추게 되었고, 불필요한 깜빡임이 사라졌다. 로딩 UI는 더 이상 개발자의 골칫거리가 아닌, 사용자를 위한 배려와 품격의 상징이 될 수 있었다.