React 19의 use() 훅과 Server Components 통합 경험

배경

React 19가 안정화되면서 use() 훅을 프로덕션에 적용하기 시작했다. 기존에는 Suspense를 활용한 데이터 페칭 패턴을 사용했는데, use()를 통해 Promise를 컴포넌트 내에서 직접 unwrap할 수 있게 되었다.

기존 방식과의 차이

기존에는 Server Component에서 async/await를 사용하거나, Client Component에서는 useEffect + useState 조합을 써야 했다.

// Server Component (기존)
async function UserProfile({ userId }: Props) {
  const user = await fetchUser(userId);
  return <div>{user.name}</div>;
}

// Client Component (기존)
function UserProfile({ userId }: Props) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  if (!user) return <Loading />;
  return <div>{user.name}</div>;
}

React 19에서는 Client Component에서도 use()로 Promise를 직접 처리할 수 있다.

'use client';

function UserProfile({ userPromise }: Props) {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

실전 적용 시 주의점

  1. Promise는 컴포넌트 외부에서 생성해야 한다. 렌더링마다 새 Promise를 만들면 무한 루프에 빠진다.

  2. Suspense 경계는 필수다. use()가 Promise를 unwrap하는 동안 가장 가까운 Suspense까지 throw한다.

  3. 에러 처리는 Error Boundary로. reject된 Promise는 에러를 throw하므로 별도 처리가 필요하다.

// 부모 컴포넌트
function Page() {
  const userPromise = fetchUser('123'); // 여기서 생성
  
  return (
    <ErrorBoundary fallback={<Error />}>
      <Suspense fallback={<Skeleton />}>
        <UserProfile userPromise={userPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

소감

use()는 데이터 페칭 로직을 더 선언적으로 만들어준다. 다만 Promise 생성 위치와 Suspense 경계 설정에 주의가 필요하다. Server Components와 조합하면 초기 로딩은 서버에서, 이후 인터랙션은 클라이언트에서 처리하는 하이브리드 패턴을 깔끔하게 구현할 수 있었다.