React.js 캐시 문제용 use() 훅

발행: (2026년 5월 24일 AM 10:26 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

대부분의 튜토리얼이 여기서 멈춥니다. 하지만 클라이언트 컴포넌트 내부에서 만든 Promise와 함께 use()를 사용하려고 하면 미묘하고 답답한 버그에 직면하게 됩니다.

// Bug: creates a new promise on every render
function UserProfile({ userId }: { userId: string }) {
  const user = use(fetchUser(userId)); // new promise every render
  return <div>{/* ... */}</div>;
}

fetchUser(userId)는 매 렌더링마다 새로운 Promise 객체를 반환합니다. React는 새로운 Promise를 보고 다시 suspend하고, 컴포넌트는 다시 렌더링되어 또 다른 새로운 Promise를 만들고, 또 suspend하는 무한 루프에 빠집니다.

use()는 데이터를 가져오는 것이 아니라 Promise를 읽는 것입니다. 그 Promise는 렌더링 사이에 안정적인 식별자를 가져야 합니다. 매 렌더링마다 새로운 Promise를 만들면 무한 suspend 루프가 발생합니다.

Promise를 안정화하는 방법

  1. 부모 컴포넌트 또는 서버 컴포넌트에서 Promise를 생성하기
// Server Component - promise created once, stable across renders
export default function UserPage({ params }: { params: { id: string } }) {
  const userPromise = fetchUser(params.id);

  return (
    <UserProfile userPromise={userPromise} />
  );
}

async/await은 필요하지 않습니다. Promise는 해결되지 않은 채로 내려보내고, 클라이언트 컴포넌트가 use()로 풀어줍니다. 서버 컴포넌트는 재렌더링되지 않으므로 Promise 참조는 자연스럽게 안정적입니다.

  1. 모듈 레벨 캐시 사용하기
const cache = new Map<string, Promise<User>>();

function fetchUserCached(id: string): Promise<User> {
  if (!cache.has(id)) {
    cache.set(id, fetchUser(id));
  }
  return cache.get(id)!;
}

function UserProfile({ userId }: { userId: string }) {
  const user = use(fetchUserCached(userId));
  return <div>{/* ... */}</div>;
}

같은 인자는 같은 Promise 참조를 반환하므로 무한 루프가 발생하지 않습니다.

캐시 래퍼에서 async 사용 금지

캐시 함수를 async로 선언하지 마세요. async 키워드는 항상 새로운 Promise를 만들기 때문에, 캐시된 값을 반환하더라도 새 Promise가 생성됩니다. 원본 Promise 객체를 저장하고 반환하는 동기 함수를 사용하세요.

  1. 데이터 패칭 라이브러리 사용하기
    TanStack Query나 SWR 같은 라이브러리는 캐싱, 중복 제거, 재검증을 기본 제공하며 use()가 등장하기 전부터 존재해 왔습니다. 다만 gzipped 기준 약 13KB의 추가 용량과 Provider 래퍼가 필요합니다. 단순히 “한 번 fetch하고 결과를 표시”하는 패턴이라면 위의 2번 옵션처럼 5줄짜리 캐시 함수만으로도 충분합니다. 라이브러리를 사용할 가치가 있는 경우는 UI에 오래 유지되는 클라이언트 상태가 있고, 탭 포커스 시 재fetch, 페이지네이션이 있는 리스트, 혹은 연관된 쿼리를 옵티미스틱하게 업데이트해야 하는 mutation 등이 있을 때입니다.

  2. 서버 컴포넌트에서 React의 cache() 사용하기

import { cache } from "react";

const getUser = cache(async (id: string): Promise<User> => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

같은 렌더링 과정에서 여러 컴포넌트가 getUser("123")를 호출하면 하나의 fetch만 수행됩니다. cache()는 단일 서버 요청의 수명 동안 반환값을 메모이제이션합니다. 페이지가 새로 로드될 때마다 캐시는 초기화됩니다.

cache() vs. useMemo

두 함수 모두 메모이제이션을 수행하지만, cache()는 서버 렌더링 시 컴포넌트 간에 데이터를 공유(중복 제거)하는 반면, useMemo는 하나의 컴포넌트 내부에서 재렌더링 사이에 계산 결과를 저장합니다. cache()는 데이터 패칭용, useMemo는 순수 연산용으로 각각 다른 도구이며, 용도도 다릅니다.

0 조회
Back to Blog

관련 글

더 보기 »

내 스킬

프로젝트를 위한 AI 지시문을 만들고, 설치하고, 관리하세요 — 코딩이 필요 없습니다. CREATE 이름을 정하고, 카테고리를 선택하고, 원하는 것을 설명하세요 — 마법사가 자동으로 구성합니다.