react-query ssr 적용 (nextjs)

Jake_Young·2022년 5월 1일
6

코드 일지, Code Archive

목록 보기
10/10

문제상황

  • 각기 다른 서비스가 공통으로 사용하는 컴포넌트를 만들어야 했다.
  • 문제는 다른 서비스의 apollo-client 버전이 다르다는 점.
  • 버전 차이에서 오는 기능 차이가 치명적인 상황이었다.
  • 이에 공통 컴포넌트 workspace는 react-query를 쓰기로 했다.
  • 패기롭게 오케이라고 외치고 작업을 시작하였다.
  • 우선 간단한 작업을 마무리한 후 react-query client를 붙일 차례가 되었다.
  • 음.. 그런데 기존 apollo client는 어떻게 붙어있을까?
  • 예제 설명은 provider만 붙이면 끝이었는데..
  • 우리 코드는 왜 이렇게 줄이 길지 ^^
  • SSR 때문인 것 같았다..
  • 그래서 apollo client 관련 코드도 읽고~
  • react-query의 ssr 관련 설정 문서도 읽어야겠다~
  • 서론이 길었다.
  • 우선 번역 및 정리부터 고~

공식문서 번역 & 정리

Prefetching

  • 그냥 사용자가 부르기 전에 미리 부를 수 있다.
  • 다만 캐시에 유효한 데이터가 있으면 부르지 않는다.
 const prefetchTodos = async () => {
   // The results of this query will be cached like a normal query
   await queryClient.prefetchQuery('todos', fetchTodos)
 }

Caching Examples

기본적인 라이프사이클

  1. 쿼리 인스턴스 (캐시가 있을 수도 없을 수도)
  2. 백그라운드 Refetching
  3. Inactive Queries
  4. Garbage Collection

cacheTime과 staleTime의 차이

  • cacheTime은 해당 쿼리가 unmount 된 이후 얼마나 캐시 위에 남아있을지 알려줌
  • cacheTime이 지나면 해당 데이터는 캐시 위에서 GC에 의해 지워짐
  • staleTime은 마운트 되어 있는 시점에서 데이터가 구식인지 판단
  • 이 내용들은 공식문서가 아닌 개발자 블로그들을 통해 정리함

자세한 예시 시나리오

  1. 캐시 타임이 5분이고 stale Time은 0인 경우
  2. useQuery('todos', fetchTodos) 이게 마운트 되면
  3. 데이터를 네트워크로 요청하고 todosfetchTodos를 키로 데이터는 캐싱된다.
  4. staleTime이 지난 뒤에는 hook이 이걸 stale이라고 표시한다.
  5. 다른 곳에서 useQuery('todos', fetchTodos) 이게 마운트 되면 이미 캐싱된 데이터가 있으므로 캐시에서 데이터를 받아서 응답을 준다.
  6. 그 이후 background refetch가 실행된다.
  7. 새로운 데이터로 결과를 업데이트 한다.(두 번 요청하진 않고 한번만 요청한다.)
  8. 두 쿼리가 동시에 unmount가 된다.
  9. cacheTime이 지나기 전에 다시 해당 쿼리가 Mount된다.
  10. 그려면 캐시에서 데이터를 가져와 보여준 이후 background refech로 실제 업데이트를 한다.
  11. 만약 둘 다 unmount된 이후, 5분(cacheTime)이 지나면 캐시에서도 데이터가 삭제된다.
  12. 요약: 요청 자체를 안 보내기 위해서는 staleTime을 길게 설정한다. 그게 아니고 cacheTime을 길게 설정하는 것은 초기 페이지 렌더링 속도를 줄여주는 것이지 요청 자체를 안 보내도록 하는 것은 아니다.

SSR

  • react-query는 서버에서 데이터를 가져오고(prefetching) queryClient에 그걸 전달하는 두 가지 방식을 지원한다.
    1. 당신이 직접 데이터를 prefetch하고 이걸 initialData 형태로 넘겨주는 것
      • 설정이 쉽지만 제약이 어느 정도 있다.
    2. 서버에서 데이터를 prefetch하고 캐시를 refresh한 다음에 이걸 client에 전달하기
      • 설정이 조금 복잡하다.
  • nextjs를 사용한다면
    • initialData를 사용하는 방법
      - getStaticPropsgetServerSideProps를 써서 useQueryinitalData 옵션으로 데이터를 전달해 줄 수 있다.
      - getServerSideProps 이름으로 된 함수를 page 디렉토리에서 export하면 nextjs는 매 요청마다 여기서 요청한 데이터를 prop로 넘겨주며 페이지를 새로 pre-render한다.
      - getStaticProps 이름으로 된 함수를 page 디렉토리에서 export하면 nextjs는 빌드 타임에 여기서 반환된 데이터를 prop으로 넘겨주며 페이지를 pre-render한다.

      // getStaticProps를 쓰는 방법
      export async function getStaticProps() {
        const posts = await getPosts()
        return { props: { posts } }
      }
      
      function Posts(props) {
        const { data } = useQuery('posts', getPosts, { initialData: props.posts })
      
        // ...
      }
    • Hydration을 사용하는 방법
      - 리액트 쿼리는 nextjs에서라면 서버에서 미리 여러개의 쿼리를 prefetching하는 기능을 제공한다. 그리고 그러한 쿼리들을 queryClient에서 dehydrating할 수 있게 한다. 이건 서버에서 즉시 사용가능한 페이지를 prerendering 할 수 있다는 것을 의미한다. 리액트 쿼리는 이후 완전한 기능을 가진 쿼리로 hydrate할 수 있게 한다. 만약 클라이언트에서 해당 데이터가 stale되어 있다고 느끼면 다시 refetching도 할 수 있다.
      - 이렇게 서버에서 쿼리를 캐싱하고 hydration 기능을 제공하려면
      1. queryClient 인스턴스를 네 app에서 생성해야 한다.
      2. 이 말은 응답 결과가 다른 유저나 다른 요청들과 공유되지 않는다는 것을 의미한다.
      3. 그리고 컴포넌트의 lifecycle에서 client가 한번만 생성되게 할 수 있다.
      4. 그리고 네 app을 로 감싼 후 client instance를 전달해라.
      5. 그 다음 네 app을 로 감싸고 pageProps로 dehydratedState를 전달해라.

       // _app.jsx
      import { Hydrate, QueryClient, QueryClientProvider } from '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>
       )
      }
    1. 그리고 원하는 페이지에서 QueryClient를 새로 만들어라.

    2. 이는 데이터를 다른 유저나 요청이 공유하지 않게 하기 위함이다.

    3. 그리고 client의 prefetchQuery 메서드를 이용해서 prefetch해라

    4. 그리고 Dehydrate를 이용해 캐시 데이터를 지우고 DehydratedState props를 전달해라.

       // pages/posts.jsx
      import { dehydrate, QueryClient, useQuery } from 'react-query';
      
      export async function getStaticProps() {
       const queryClient = new QueryClient()
      
       await queryClient.prefetchQuery('posts', getPosts)
      
       return {
         props: {
           dehydratedState: dehydrate(queryClient),
         },
       }
      }
      
      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)
      
       // ...
      }

      느낀점

    • 쉽지 않네..
    • SSR도 다시 공부해야겠다..
profile
자바스크립트와 파이썬 그리고 컴퓨터와 네트워크

0개의 댓글