반응형
snowman95
코딩수련장
snowman95
전체 방문자
오늘
어제
  • 분류 전체보기 (230)
    • 앱테크 (3)
    • 옵시디언 (5)
    • 드라마, 영화 (1)
    • 개발자 이야기 (24)
    • 프로젝트 (10)
      • 프로젝트 방법론 (7)
      • 프로젝트 기록 (2)
      • Github (1)
    • 개발 지식 (0)
      • 디자인 패턴 (0)
    • 프론트엔드 개발 (5)
      • 테크트리 (2)
      • React.js (19)
      • ReactNative (2)
      • Next.js (6)
      • GraphQL (6)
      • 패키지 매니저 (2)
      • 라이브러리 (3)
      • 상태관리 라이브러리 (4)
      • Web 지식 (3)
      • HTML CSS (26)
      • Javascript (16)
      • 도구 (Tool) (3)
      • 성능 최적화 (1)
      • 디자인시스템 (0)
    • Python (53)
      • 모음집 (1)
      • 문법 (12)
      • 라이브러리 (15)
      • 알고리즘 (10)
      • 백준 문제풀이 (9)
      • 코딩테스트 (2)
      • 도구 (Tool) (3)
    • C++ (20)
      • 알고리즘 (6)
      • 삼성SW기출 (6)
      • 삼성 A형 (6)
    • 데이터사이언스 (1)
    • 인프라 (9)
      • 하드웨어 지식 (4)
      • Ansible (2)
      • Database (2)
      • 쉘스크립트 (1)
    • 주식 (0)
    • 취업 준비 (4)
      • 취업 이야기 (0)

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • C++
  • 티스토리챌린지
  • 오블완
  • Next.js #graphql #tailwind.css
  • 전공 요약 #데이터베이스
  • 개발자취업시장
  • 개발자이직회고
  • 25년도채용시장
  • 알고리즘
  • 기계식키보드 #nuphy
  • 언어
  • A형
  • 나의 해방일지
  • 면접
  • 백준
  • 전공 요약 #네트워크
  • 개발자이직
  • 삼성SDS
  • 전공요약
  • 삼성SW역량테스트

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
snowman95

코딩수련장

Next.js 13 버전에서 ReactQuery 사용시 서버 컴포넌트에서 클라이언트 컴포넌트로 pre-fetch 데이터 전달하는 방법
프론트엔드 개발/Next.js

Next.js 13 버전에서 ReactQuery 사용시 서버 컴포넌트에서 클라이언트 컴포넌트로 pre-fetch 데이터 전달하는 방법

2023. 2. 5. 20:18
728x90
반응형

Next.js 13 의 클라이언트 컴포넌트에서 react-query 를 사용하는 경우

서버 컴포넌트에서 데이터를 미리 가져온 뒤 클라이언트 컴포넌트에 전달하는 것에 대한 게시물 입니다.

 

코드 예시의 라이브러리 버전은 아래와 같습니다.

"@tanstack/react-query": "^4.24.4"
"@tanstack/react-query-devtools": "^4.24.4",
"next": "13.1.3",
"react": "18.2.0",

 

App 디렉토리의 서버컴포넌트에서 데이터를 미리 가져온 뒤 클라이언트 컴포넌트에 전달하는 방법이 2가지가 있습니다.

각각 차례대로 살펴보겠습니다.

  1. props drilling 방식으로 pre-fetch
  2. hydrate 방식으로 pre-fetch

 

react-query Provider 설정

가장 먼저 클라이언트 컴포넌트로 QueryClientProvider Wrapper 컴포넌트를 만들어 줍니다.

최상위 컴포넌트의 body 테그 안에 들어가는 children 을 이 Wrapper 컴포넌트로 감싸주어야 합니다. 

// app/ReactQueryProvider.tsx
"use client";

import {
  QueryClient,
  QueryClientProvider,
  Hydrate,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { PropsWithChildren, useState } from "react";


export default function ReactQueryProvider({ children }: PropsWithChildren) {
  const [queryClient] = useState(() => new QueryClient());

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}
// app/layout.tsx

import "../styles/globals.css";
import ReactQueryProvider from "./ReactQueryProvider";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <head />
      <body className="overflow-y-scroll">
        <ReactQueryProvider>{children}</ReactQueryProvider>
      </body>
    </html>
  );
}

 

방법 1: props drilling 방식으로 pre-fetch 

단순한게 장점이고 props drilling 이 생기는게 단점입니다.

// app/page.jsx
export default async function Home() {
  const initialData = await getPosts()
  
  return <Posts posts={initialData} />
}

서버 컴포넌트에서 클라이언트 컴포넌트로 props 를 전달해줍니다.

그리고 useQuery option인 initialData 에 props로 받아온 데이터를 전달해줍니다.

 

무척 간단합니다. 정말 간단히 만들거면 이 방법이 좋아 보이긴 합니다.

// app/posts.jsx
'use client'

import { useQuery } from '@tanstack/react-query'

export function Posts(props) {
  const {data} = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    initialData: props.posts, // props 로 전달받은 값을 넣어줍니다.
  })
  
  // ...
}

 

방법 2: hydrate 방식으로 pre-fetch 

별도 파일을 만들어 줘야하는게 (귀찮은게) 단점입니다. 

대신에 props drilling 없이 미리 가져온 쿼리를 컴포넌트 트리 아래의 모든 컴포넌트에서 사용할 수 있는게 장점입니다.

 

QueryClient 의 request-scoped 싱글톤 인스턴스를 만들어줍니다.

쉽게 말해서 단 1개의 QueryClient 를 돌려쓰겠다는 말입니다.

// getQueryClient.tsx
import { QueryClient } from "@tanstack/query-core";
import { cache } from "react";

// `QueryClient` 의 요청 범위 싱글톤 인스턴스를 만듭니다 .
// 이렇게 하면 서로 다른 사용자와 요청 간에 데이터가 공유되지 않고 여전히 요청당 한 번만 QueryClient를 생성합니다.
const getQueryClient = cache(() => new QueryClient());
export default getQueryClient;

 

react-query 의 Hydrate 요소를 클라이언트 컴포넌트로 래핑해서 클라이언트 컴포넌트에서 사용할 수 있도록 만들어 줍니다.

추후에 'use client' 지시문이 react-query 에 추가된다면 이 파일은 필요 없어질겁니다.

// hydrateOnClient.tsx
"use client";

import { Hydrate as HydrateOnClient } from "@tanstack/react-query";
export default HydrateOnClient;

 

이번 예시에서는 /app 디렉토리 아래에 posts 디렉토리를 만들겁니다.

 

posts 클라이언트 컴포넌트에 미리 가져온 데이터를 전달 하기 위한 hydratedPosts 서버 컴포넌트를 작성해줍니다.

미리 가져온 쿼리를 사용하는 클라이언트 컴포넌트보다 컴포넌트 트리에서 상위에 있는 서버 컴포넌트에서 데이터를 가져옵니다.
미리 가져온 쿼리는 컴포넌트 트리 아래의 모든 컴포넌트에서 사용할 수 있습니다.

 

아래와 같은 과정으로 동작합니다.

  • `QueryClient` 싱글톤 인스턴스 검색
  • 클라이언트의 prefetchQuery 메서드를 사용하여 데이터를 미리 가져오고 완료될 때까지 기다립니다.
  • 'dehydrate' 를 사용 하여 쿼리 캐시에서 pre-fetch 된 쿼리의 hydrate State를 얻습니다.
  • `<Hydrate> 클라이언트 컴포넌트 내부에 pre-fetch 된 쿼리가 필요한 컴포넌트 트리를 래핑하고 dehydrate 상태를 제공합니다.
import { API } from "@/api/api";
import { QUERY_KEYS } from "@/api/queries";
import { dehydrate } from "@tanstack/query-core";
import getQueryClient from "../../api/hooks/getQueryClient";
import HydrateOnClient from "../../api/hooks/hydrateOnClient";
import Posts from "./posts";

export default async function HydratedPosts() {
  const queryClient = getQueryClient();
  await queryClient.prefetchQuery([QUERY_KEYS.POST.조회], API.POST.조회);
  const dehydratedState = dehydrate(queryClient);
  return (
    <HydrateOnClient state={dehydratedState}>
      <Posts />
    </HydrateOnClient>
  );
}

 

가져온 데이터를 보여주기 위한 UI 에 해당하는 Posts 클라이언트 컴포넌트를 만들어줍니다.

useQuery 의 초기 값으로 dehydratedState 상태를 가져오게 됩니다. 

서버 렌더링 중에 <Hydrate> 클라이언트 컴포넌트 내에 중첩된 useQuery 에 대한 호출은 상태 속성에 제공된 미리 가져온 데이터에 액세스할 수 있습니다.

여기서 useGetPosts 는 useQuery 를 한번더 래핑해준 custom hook 입니다. 

"use client";

import { useGetPosts } from "@/api/hooks/useGetPosts";
import { SkeletonCard } from "@/ui/SkeletonCard";

export default function Posts() {
  const { data, isFetching } = useGetPosts({ queries: [], enabled: true });

  return (
    <section className="flex flex-col gap-5">
      {data?.map((item) => {
        return (
          <article key={item.id} className="flex flex-col border-2 ">
            <span className="font-bold">{`id: ${item.id}`}</span>
            <span>{`userId: ${item.userId}`}</span>
            <span>{`title: ${item.title}`}</span>
            <span>{`body: ${item.body}`}</span>
          </article>
        );
      })}
    </section>
  );
}

 

/posts 라우트의 index.tsx 역할을 하 page 서버 컴포넌트에 HydratePosts 서버 컴포넌트를 추가합니다.

참고로 TypeScript는 현재 비동기 서버 컴포넌트를 사용할 때 유형 오류에 대해 불평합니다. 그래서 임시 해결방법으로

{/* @ts-expect-error Server Component */} 를 추가해줘야 합니다.

- 자세한 내용은 이 링크 참고

app/posts/page.tsx 

import HydratedPosts from "./hydratedPosts";

export default function Page() {
  return (
    <main className="flex w-full">
        {/* @ts-expect-error Server Component */}
        <HydratedPosts />
    </main>
  );
}

 

이렇게 하고 네트워크 탭을 확인해보았을때

실제로 클라이언트 상에서는 아무런 API 요청이 없음에도 데이터를 가져온 화면을 보여주는 것을 볼 수 있습니다 !

 

이런 방식으로 서버에서 왠만한 데이터를 다 불러오고 클라이언트에서는 최소한의 API 요청을 하게된다면

사이트가 엄청나게 개선이 될 것 같습니다.

추가로 graphql 기술을 적용하여 데이터에서 꼭 필요한 필드만 뽑아 온다면 HTML 파일의 용량이 줄어들어서 더 큰 개선이 기대됩니다.

 

 

 

꿀팁 하나더 추가합니다.

useInfiniteQuery 에 prefetchQuery 를 사용하려면 prefetchQuery의 응답값을 변환해서 넣어줘야 합니다 !

  await queryClient.prefetchQuery([QUERY_KEYS.PHOTO.조회], () =>
    API.PHOTO.조회({ page: 0 }).then((data) => {
      return {
        pages: [data],
      };
    })
  );
반응형
저작자표시 비영리 동일조건 (새창열림)

'프론트엔드 개발 > Next.js' 카테고리의 다른 글

[프로젝트 기록] Next.js 의 SSG 는 너무 느려서 못써먹겠다.  (4) 2024.09.30
Next.js + React Native 크로스 플랫폼 개발을 위한 Solito, NativeWind 소개  (0) 2023.10.03
Next.js 에서 소셜 로그인 구현하기 (next-auth.js 사용)  (1) 2023.02.18
웹 프레임워크 Next.js 는 무엇인가요? (프론트 면접 질문)  (0) 2023.02.05
Next 13 버전에서 App 디렉토리 사용방법 및 소개  (3) 2023.01.30
    '프론트엔드 개발/Next.js' 카테고리의 다른 글
    • Next.js + React Native 크로스 플랫폼 개발을 위한 Solito, NativeWind 소개
    • Next.js 에서 소셜 로그인 구현하기 (next-auth.js 사용)
    • 웹 프레임워크 Next.js 는 무엇인가요? (프론트 면접 질문)
    • Next 13 버전에서 App 디렉토리 사용방법 및 소개
    snowman95
    snowman95
    (17~19) Unity/Unreal Engine 게임 프로그래머 (20~21) System Administrator ___________ (22~) React 웹 프론트앤드 개발자 __________ 깃헙 : https://github.com/snowman95

    티스토리툴바