next.js 에서 tanstack query 설정하기

I n ru n·2024년 4월 22일
2
  • 본 글의 nextjs 버전은 app router 기반입니다.
  • tanstack query 는 v5 입니다.

provider 설정

// app/providers.jsx
'use client'

// 서버 컴포넌트에선 usehook 을 사용할수 없기에 'use client' 를 포함한 별도의 파일로 분리
import { useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // ssr 일경우 보통 staleTime 을 지정해야한다.
        // 클라이언트에서 즉시 refetch 되는것을 방지하기위해 0 위로 설정
        staleTime: 60 * 1000,
      },
    },
  })
}

let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
  if (typeof window === 'undefined') {
    // Server: 항상 새로운 queryClient 생성
    return makeQueryClient()
  } else {
    // Browser: 다시 만들지 않고 기존에 이미 client 존재시 해당 client 제공
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}

export default function Providers({ children }) {
  // NOTE: suspense boundary 로 로딩 이 잡히지 않는경우, useState 를 사용하여 초기화한다면, clinet 는 유실된다.
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}
// app/layout.jsx
import Providers from './providers'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

prefetch, de/hydragte 하기

// app/posts/page.jsx
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from '@tanstack/react-query'
import Posts from './posts'

export default async function PostsPage() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
  })

  return (
    // HydrationBoundary 는 클라이언트 컴포넌트이다. 따라서 hydration 은 클라이언트 에서 실행된다.
    // server 에서 prefetch 된 queryClient 를 client 로 내려준다.
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Posts />
    </HydrationBoundary>
  )
}
// app/posts/posts.jsx
'use client'

export default function Posts() {
  // 이 useQuery 는 즉시 data 사용이 가능하다.
  const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })

  // server component 에서 prefetch 하지 않았으므로, client 에서 fetch 할때까지 data 사용이 불가하다. 위 useQuery 즉 prefetch 된 쿼리와 함께 섞어사용가능하다.
  const { data: commentsData } = useQuery({
    queryKey: ['posts-comments'],
    queryFn: getComments,
  })

  // ...
}

서버 컴포넌트에서 prefetch 할수있다.

중첩된 서버 컴포넌트

// app/posts/page.jsx
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from '@tanstack/react-query'
import Posts from './posts'
import CommentsServerComponent from './comments-server'

export default async function PostsPage() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Posts />
      <CommentsServerComponent />
    </HydrationBoundary>
  )
}

// app/posts/comments-server.jsx
import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from '@tanstack/react-query'
import Comments from './comments'

export default async function CommentsServerComponent() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery({
    queryKey: ['posts-comments'],
    queryFn: getComments,
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Comments />
    </HydrationBoundary>
  )
}

<HydrationBoundary> 는 중첩된 컴포넌트 각각 중첩 사용이 가능하다.

위 코드는 server 에서 prefetch 를 총 두번하지만 중첩되기에
server 사이드에서 waterfall 발생

1. |> getPosts()
2.   |> getComments()

next js 에서 parellel routes를 사용한다면 waterfall 을 평탄화할수있다.

단일 queryClient

// app/getQueryClient.jsx
import { QueryClient } from '@tanstack/react-query'
import { cache } from 'react'

// cache() 는 요청별로 저장된다.
const getQueryClient = cache(() => new QueryClient())
export default getQueryClient

server 컴포넌트에서 단일한 queryClient 객체를 사용할수있다.

참고

https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr

0개의 댓글

관련 채용 정보