Published on

ReactQuery in Nextjs

Authors
  • avatar
    Name
    Inhwan Cho
    Twitter

React Query 설치하기

# 리액트 쿼리
$ npm i @tanstack/react-query
# 디버깅을 위한 dev-tool
$ npm i @tanstack/react-query-devtools
# eslint 필요 시 아래도 설치
$ npm i -D @tanstack/eslint-plugin-query
# nextjs에서 스트림 사용시 아래도 설치(실험 버전 - 코드 간략화 됨)
$ npm i @tanstack/react-query-next-experimental

React Query vs SWR

React QuerySWR은 모두 데이터 페칭, 캐싱, 동기화를 쉽게 관리할 수 있게 도와주는 React 라이브러리입니다.

기능Tanstack QuerySWR
데이터 페칭 및 캐싱자동 캐싱과 데이터 재사용, 백그라운드 업데이트 지원자동 캐싱 및 재검증으로 최신 상태 유지
서버 상태 동기화다양한 캐싱 전략과 상세 설정 가능데이터 변화 시 자동 재검증으로 동기화
백그라운드 업데이트지원, 구성에 따라 세밀하게 조절 가능지원, 데이터 포커스 재검증 등을 통해 최신 상태 유지
페이지네이션 및 무한 스크롤구성이 용이한 훅 제공무한 로딩 훅 (useSWRInfinite) 제공
쿼리 취소 및 실패 관리요청 취소와 재시도 관리 가능요청 재시도는 지원하지만 취소는 제한적
사용 용이성광범위한 기능 제공, 세밀한 설정 가능단순하고 직관적인 API, 쉬운 시작

데이터 패칭에 세밀한 제어가 필요하지 않다면, SWR의 간단하고 빠른 접근 방식이 더 적합할 수 있습니다.

반면, React Query에는 강력한 디버깅 툴(dev-tools)과 복잡한 데이터 관리와 캐싱 전략이 필요한 경우 좋은 선택이 될 수 있습니다.

React Query는 TanStack 데이터 관리 라이브러리에 포함된 일부로 TanStack Query로 브랜드가 변경되었습니다. 즉, react-query == tanstack-query라고 생각하시면 됩니다.

Nextjs에서 Tanstack Query 사용하기

prefetching 방법 정하기

prefetching 방법에는 두 가지가 있습니다

  • initialData: 서버에서 데이터를 미리 가져오고 이를 클라이언트 컴포넌트로 prop으로 전달합니다. 설정이 간단하며 빠르게 적용할 수 있지만, prop drilling이 필요할 수 있습니다.

  • <Hydrate>: 서버에서 데이터를 미리 가져오고, 이 데이터를 dehydrate하여 클라이언트에서 rehydrate합니다. 더 복잡한 설정을 요구하지만, 서버에서 prefetch된 시점을 기준으로 데이터를 관리합니다.

v5에서는 <HydrationBoundary>으로 용어가 변경되었습니다

용어 설명

  • Prop Drilling: React에서 데이터를 부모 컴포넌트에서 자식 컴포넌트로 계속해서 전달하는 것을 말합니다.

  • hydrate: Nextjs에서는 초기 HTML 파일(더미 HTML)을 먼저 전달하고 이후, HTML 요소들을 React 컴포넌트로 변환 및 이벤트리스너 등을 부착하여 React DOM에서 관리하게 하는데 이 과정을 Hydration이라고 합니다.

즉, 기본 HTML을 먼저 받고, 추후 js를 이용하여 interactive하게 변경하는걸 Hydration이라 합니다.

use client가 적힌 컴포넌트를 CSR이라 하는데 이 CSR만 Hydration 작업을 수행하게됩니다. 즉 use client가 적힌 컴포넌트는 interactive 하다는 의미이지, client에서 rendering 된다는 의미가 아닙니다. backend에서 rendering되고 frontend에서 Hydrate됩니다.

  1. QueryClientProvider 설정 및 컴포넌트 감싸기
app/providers.tsx
'use client'
import { useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
// 디버깅 필요 시
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // reretching time setting
        staleTime: 60 * 1000,
      },
    },
  })
}
let browserQueryClient: QueryClient | undefined = undefined

// Server: 항상 새로운 쿼리 생성
// Browser: 클라이언트가 없으면 새 쿼리 생성
function getQueryClient() {
  if (typeof window === 'undefined') {
    return makeQueryClient()
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}

export default function Providers({ children }) {
  // 공식문서에서는 suspense를 사용하지 않는다면 useState를 사용하지 말라고 권고 하고 있습니다.
  const queryClient = getQueryClient()
  return <QueryClientProvider client={queryClient}>
            {children}
            <ReactQueryDevtools initialIsOpen={false} />
          </QueryClientProvider>
}
app/layout.tsx
import Providers from '@/providers'
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
app/page.tsx
'use client'
import { useQuery } from "@tanstack/react-query";

export default function Page() {

// 
const { data, isLoading } = useQuery({
  queryKey: ["stream-hydrate-users"],
  queryFn: () => fetch('https://jsonplaceholder.typicode.com/todos').then((res) => res.json())
});
const { data as SuspenseData } = useSuspenseQuery({'동일 값'})

return // ...
    { isLoading ? '...' : data.title }
<Suspense
  fallback={
    <p>loading...</p>
  }>
  <DataComponent/>   //SuspenseData
</Suspense>
}
  • 단, Suspense를 사용할땐, v4 이하에선 useQuries, v5부터는 useSuspenseQuery를 사용하면 병렬로 호출 할 수 있습니다.

Client Stream Hydration 사용하기

  • Nextjs에서 리액트 쿼리를 사용하는 이유 중 하나가 바로 캐싱 기능이었는데, Next.js 13이후 에서 cache 옵션을 사용해 렌더링 방식을 적용하도록 하기때문에 비교적 간단한 SWR을 많이 사용합니다.

Client Stream Hydration이라는 기능이 나왔습니다.

설정 방법은 기존과 유사하나 컴포넌트를 QueryClientProvider로 감싸기 전에 ReactQueryStreamedHydration도 감싸야 합니다.

app/providers.tsx
// 나머지는 기존과 동일
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";

return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>
        {children}
      </ReactQueryStreamedHydration>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );

그리고 data를 client에서 불러올때 suspense : true를 해줘야 합니다.

app/page.tsx
'use client'
import { useQuery } from "@tanstack/react-query";

export default function Page() {
const { data } = useQuery<Todo[]>({
  queryKey: ["stream-hydrate-users"],
  queryFn: () => fetch('https://jsonplaceholder.typicode.com/todos').then((res) => res.json()),
  suspense: true, // true 설정
  staleTime: 5 * 1000,
});

return // ...
<Suspense
  fallback={
    <p>loading...</p>
  }>
  <DataComponent>
</Suspense>
}

단, 이 기능을 사용하면 SEO에서 불리합니다.

그래서 Infinity scroll 같은 복잡한 기능이 필요없다면, Nextjs app-router의 경우, 굳이 react-query만 사용할 필요는 없고,

데이터를 가져올때는 react-query를 사용하여 서버 컴포넌트로 가져오고, 데이터 전달은 server action을 사용해도 됩니다.