// 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>
)
}
// 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 을 평탄화할수있다.
// 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