React Query와 SSR

송은·2023년 6월 15일
0
post-thumbnail

Next.js는 서버 사이드에서 데이터 Prefetching이 가능하다. Prefetch 하게 되는 데이터는 HTML 페이지가 클라이언트에게 전송되기 전에 준비되어 HTML에 포함되어 렌더링된다.

React-Query는 Next.js의 서버 사이드에서 데이터를 Prefetch하여 queryClient로 넘겨주는 기능을 제공한다.

따라서 한 번 사이트가 로딩된 후에는 로딩 시간이 크게 단축된다는 점, SEO에 좋다는 장점이 있다.

사실 진행하고 있는 토이 프로젝트의 데이터는 데이터가 크지 않기 때문에 로딩 시간에 큰 차이가 없겠지만, Next.js를 이용한 SSR을 React-Query를 이용해서 데이터를 패치해보고 싶어서 알아보게 되었다.

공식 문서에서 보면 React-Query를 이용하여 SSR에서 데이터를 prefetch 하기 위해 두 가지 방법을 지원한다. 이 때 다음 두 가지가 지원되는 Next.js 에서 사용하기를 권장하고 있다.

  • Static Generation (SSG)
  • Server-side Rendering (SSR)

1. Initial Data

Next.js의 getStaticProps 또는 getServerSideProp에서 원하는 API를 요청해서 데이터를 패치한 다음, 응답을 페이지에 props로 넘겨주어 useQuery의 initialData로 설정하는 방법이다.

function Posts(props) {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    initialData: props.posts, // initialData 로 설정
  });
}

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

장/단점

  • 간단하다
  • 더 깊은 컴포넌트에서 useQuery 를 사용할 경우 하위 컴포넌트로 props를 계속해서 건네주어야 한다. (props-drilling)
  • 같은 응답을 하는 query가 여러개인 경우 initialData를 다 넣어주어야 한다.
  • 서버에서 쿼리를 가져온 시간을 알 수 없어서 데이터가 업데이트 된 시간이나 쿼리를 다시 가져와야 하는지에 대한 여부를 페이지가 로드 된 시간을 기반으로 결정해야 한다.

✅ 그래서 React-query에서도 두 번째 방법을 추천한다.


2. Hydration

동일하게 getStaticPropsgetServerSideProps에서 데이터를 prefetch를 하고, queryClient를 dehydrate하여 페이지에 dehydratedState로 내려주면 된다.

사용하기에 앞서 다음과 같이 app 컴포넌트를 <QueryClientProvider>, <Hydrate> 로 감싸서 설정해주어야 한다.

// _app.jsx
import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';

export default function MyApp({ Component, pageProps }) {
  const [queryClient] = React.useState(() => new QueryClient());

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  );
}
import { dehydrate, QueryClient, useQuery } from 'react-query';

function Posts() {
  // This useQuery could just as well happen in some deeper child to
  // the "Posts"-page, data will be available immediately either way
  const { data } = useQuery('posts', getPosts);

  // This query was not prefetched on the server and will not start
  // fetching until on the client, both patterns are fine to mix
  const { data: otherData } = useQuery('posts-2', getPosts);

  // ...
}

export async function getStaticProps() {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery('posts', getPosts);

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
}
  1. prefetch 해서 queryClientdehydrate한다. (html 렌더링 전 데이터 패치)
  2. 서버사이드 렌더링 시 prefetch 할 때와 같은 key를 가지는 query를 만나면 캐싱된 데이터를 반환하고 이에 따라 렌더링을 진행한다.
  3. 서버사이드 렌더링을 진행한 HTML 파일을 클라이언트로 보내준다.
  4. 클라이언트에서 hydrate 진행 시 queryClient 에서 dehydrate했던 데이터를 바탕으로 hydrate를 진행한다.

❓ dehydrate
queryClientdehydrate해서 props에 내려주고 있다. 이 때 dehydrate는 쿼리 클라이언트의 상태를 serialize(직렬화)하는 역할이라고 보면 된다.

dehydrate 함으로써 클라이언트-서버간 전송해야하는 데이터의 양이 줄어들고, 애플리케이션의 성능을 최적화할 수 있다는 장점이 있다.

🤔 정보를 보내기 위해서는 전송 가능한 형태로 만드는 것(serialize)이 필요하다. 이 때 dehydrate(탈수) 물기를 빼주어야 전달하기 쉽고 전달해줄 수 있다라고 생각하면 쉽다.


❓ hydrate
hydrate란 NextJS 개념은 아니고 React 개념으로, DOM 요소에 자바스크립트 속성을 매칭시키기 위한 것을 말한다.

즉, NextJS에서는 서버 사이드에서 pre-rendering한 HTML 파일들을 서버사이드 렌더링 형식으로 보내주고, 클라이언트 사이드에서 React 코드를 통해 hydrate를 진행한다.

또한 hydrate를 진행해도 단순히 DOM에 JS 속성을 매칭시키는 일이기 때문에 paint가 다시 일어나진 않는다.

👉 서버사이드에서 내려주는 HTML은 자바스크립트 이벤트 리스너들이 붙어있지 않은데, hydrate 단계에서 이런 부분들을 다시 붙여주게 된다.

🤔 서버단에서 dehydrated 되어서 온 데이터를 물기가 빠져있으므로 다시 물을 부어주는 역할이다. hydrate를 진행해주어 스크립트 코드들을 매칭시켜준다.


dehydratedState를 page에 props로 직접 넘겨주어 읽어올 순 없나?

function Posts({dehydratedState}) { // 이런식으로 바로 넘겨줄 순 없나?
	console.log(dehydratedState); // undefined
	...
}

export async function getSeverSideProps() {
 const queryClient = new QueryClient()
 await queryClient.prefetchQuery('posts', getPosts);

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

이렇게 넘겨주어서 읽어오게되면 dehydratedStateundefined가 뜨게 된다.

prefetch한 요청값이 담겨있지 않을까 했는데 dehydratedState는 Next.js 페이지의 props로 직접 전달되지 않는다고 한다.

대신 서버에서 클라이언트로 데이터를 전달해야 하는 경우에는 페이지 구성 요소에서 getInitialProps를 이용하여 데이터를 가져와서 컴포넌트에게 props로 전달할 수 있다.




출처

profile
개발자

0개의 댓글