React Query prefetching 데이터 사용하기

KyungminLee·2024년 3월 25일
0

Next

목록 보기
3/5
post-thumbnail

Next.js나 Remix 같은 프레임워크 내에서 React-Query를 사용한다면, 서버 렌더링 될 때 요청 후 응답받은 데이터를 SPA 방식으로 전환되고 나서도 유지할 수 있을까?? 어떻게 가능할까??
React Query의 hydratedehydrate는 서버에서 미리 가져온 데이터를 클라이언트 사이드에서 재사용 할 수 있게 해준다. 이번 글을 통해 서버 렌더링과 어떻게 이를 가능하게 하는지 hydrate와 dehydrate에 대해서 알아봅시다.

1. Server Rendering

서버 렌더링은 사용자가 페이지를 로드하는 즉시 볼 수 있는 초기 HTML을 서버에서 생성하는 행위입니다. 이는 페이지 요청 시 즉시 발생할 수 있으며(SSR), 이전 요청이 캐시 되었거나 빌드 시간에 미리 생성(SSG) 할 수도 있다.

클라이언트 렌더링 애플리케이션에서는 사용자에게 화면에 콘텐츠를 표시하기 전에 최소 3번의 서버 왕복이 필요하다.

1. 빈 html을 받아온다.(markup)
2. JS(chunk download)
3. Query(fetch)

서버 사이드 렌더링 방식은 아래와 같이 줄어든다.

1. 내용이 담겨 있고 데이터가 초기화 되어있음(markup)
2. JS(chunk download)

1번이 완료되면 사용자는 콘텐츠를 볼 수 있고, 2번이 끝나면 페이지가 상호작용 가능하고 클릭할 수 있게 된다. 마크업에 필요한 초기 데이터가 포함되어 있기 때문에, 적어도 데이터를 다시 검증할 필요가 있을 때까지는 클라이언트에서 3번을 실행할 필요가 없다.

서버 렌더링을 통해 1번 과정에서 내용이 채워져 있고 data가 초기화되어있는 html을 생성하기 위해서는 마크업을 생성/렌더링 하기 전에 해당 데이터를 미리 가져와야(prefetch) 하며, 데이터를 직렬화 가능한(serializable) 형식으로 dehydrate 시켜 마크업에 포함(embed) 시키고, 클라이언트에서는 React Query 캐시로 해당 데이터를 hydrate 하여 새로운 fetch를 클라이언트에서 추가적으로 할 필요가 없도록 해야 한다.

2. initialData 를 사용하여 서버에서 fetch한 데이터 사용하기

리액트쿼리에서 initalData를 넣는 방식은 아래와 같다.

  • 서버에서 데이터를 prefetch하고, 컴포넌트에 initialData로 전달하는 방법.
  • Next.js의 getStaticProps 또는 getServerSideProps를 사용하면, 가져온 데이터를 useQuery의 initialData 옵션으로 넣어줄 수 있다.
// Next.js 페이지 라우터 예시
function Posts(props) {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    initialData: props.posts,
  })
}

export async function getServerSideProps() {
  const posts = await getPosts()
  return { props: { posts } }
}

위 코드에든 몇가지 문제점이 있다.

  • 트리 매우 아래쪽에 있는 컴포넌트에서 useQuery를 호출하는 경우, initialData를 그 지점까지 전달해야 한다.(props dilling)
  • 여러 위치에서 동일한 쿼리를 사용해 useQuery를 호출하는 경우 모든 위치에 initialData를 전달해야 한다.
  • 서버에서 쿼리가 언제 fetch 되었는지 알 수 없으므로, dataUpdatedAt와 쿼리의 refetching 여부는 페이지가 로드된 시점에 따라 결정됩니다
  • 이지를 여러 번 왔다 갔다 하면, 매번 getServerSideProps가 호출되어 새로운 데이터를 가져오지만, initialData 옵션을 사용하기 때문에 클라이언트 캐시와 데이터는 결코 업데이트되지 않는다.(즉 초기 데이터가 들어와도 클라이언트 캐시에 저장된게 아니기 때문에 api를 재 요청한다)

이러한 단점은 Hydration을 사용하여 해결할 수 있다.

3. Hydration

서버에서 쿼리를 prefetch(캐싱)하고 캐시를 dehydrate한 후 클라이언트에서 rehydrate하는 방법

React Query에서는 dehydratehydrate 함수를 제공하여 이 과정을 간소화합니다.

Hydrate와 Dehydrate의 역할

dehydrate는 서버에서 React Query의 상태를 클라이언트로 전송할 수 있는 형태로 만들기 위해 사용된다. 서버에서 데이터를 가져온 후, 이 데이터를 직렬화(serialization) 하여 클라이언트로 전송합니다. 직렬화된 데이터는 DehydratedState 형태로 표현되며, 클라이언트 측에서 hydrate 함수를 통해 다시 React Query 상태로 변환됩니다.

hydrate는 클라이언트 측에서 직렬화된 상태를 받아 이를 React Query의 상태로 변환한다. 이 과정은 서버에서 미리 가져온 데이터를 클라이언트의 쿼리 캐시에 적용하여, 네트워크 요청 없이 데이터를 사용할 수 있게 한다.

Hydrate, Dehydrate 사용예시

해당 예시는 Next app routing(서버컴포넌트) 와 page routing 방식 둘 다 사용했습니다

Next page routing 방식에서는 getServerSideProps 와 같은 함수를 사용해 페이지 컴포넌트에서 props를 통해 주입받는다.

export async function getServerSideProps() {
  const queryClient = new QueryClient()

  const user = await queryClient.fetchQuery({
    queryKey: ['user', email],
    queryFn: getUserByEmail,
  })


  if (user?.userId) {
    await queryClient.prefetchQuery({
      queryKey: ['projects', userId],
      queryFn: getProjectsByUser,
    })
  }

  return { props: { dehydratedState: dehydrate(queryClient) } }
}

App routing 방식은 서버컴포넌트에 아래와 같은 예시로 작성하면 된다.

export default async function HydratedFunc() {
  const queryClient = getQueryClient();
  
  // 데이터를 서버(서버컴포넌트)에서 불러옴 prefetch
  await queryClient.prefetchQuery('KeyList', () =>
    getList(),
  );

  const dehydratedState = dehydrate(queryClient);

  return (
    <Hydrate state={dehydratedState}>
      <Component />
    </Hydrate>
  );
}

결론

React Query의 hydratedehydrate는 서버 사이드 렌더링(SSR) 혹은 서버컴포넌트를 구현하는 데 필수적인 도구입니다. 이들 기능은 서버에서 클라이언트로 데이터를 원활하게 전달하고, 애플리케이션의 성능을 최적화하는 데 중요한 역할을 합니다.
dehydrate를 사용하여 서버에서 데이터를 추출하고, hydrate를 통해 클라이언트에서 이를 재사용함으로써 네트워크 오버헤드를 줄이고, 서버와 클라이언트 간의 데이터 일관성을 유지할 수 있습니다. 이러한 접근 방식은 효율적이고 원활한 SSR 경험을 제공하며, 사용자에게 더욱 빠르고 매끄러운 인터페이스를 제공합니다.

profile
끊임없이 발전해가는 개발자.

0개의 댓글

관련 채용 정보