React Query์ SSR: ์๋ฒ ์ฌ์ด๋ ๋ฐ์ดํฐ ๊ด๋ฆฌ์ Hydration ํ์ฉ
ํ์ฌ ํ๋ก์ ํธ๋ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR)๊ณผ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ ๋๋ง(CSR)์ด ํผํฉ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ค.
- ์ ์ ์ธ ํ์ด์ง: ๋ฐ์ดํฐ ๋ณํ๊ฐ ์ ์ด ์ ์ ์ฝํ ์ธ ๋ก ์ ๊ณต (e.g. ๋ฉ์ธ ํ๋ฉด)
- ๋์ ์ธ ํ์ด์ง: ๋ฐ์ดํฐ ๋ณํ๊ฐ ๋ง์ ์ ๋ฐ์ดํธ๊ฐ ์ฆ์ (e.g. ๊ฒ์๋ฌผ ๋ฆฌ์คํธ)
์ด๋ฌํ ์ํฉ์์, ๋๋ ์ ์ ์ธ ํ์ด์ง๋ Next.js์ ์บ์ ๊ธฐ๋ฅ, ๋์ ์ธ ํ์ด์ง๋ React Query๋ฅผ ์ฌ์ฉํ๋ฉด ์ข์ ๊ฒ์ด๋ผ ์๊ฐํ์๋ค.
ํ์ง๋ง ๊ด๋ฆฌ์ ๋ณต์ก์ฑ์ ์ค์ด๊ธฐ ์ํด ํ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ํต์ผํ๋ ๊ฒ์ด ์ข๋ค๋ ํผ๋๋ฐฑ์ ๋ฐ์๋ค.
๊ทธ๋์ React Query๋ฅผ SSR ํ๊ฒฝ์์๋ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํ๋ค.
React Query
React Query์ useQuery
๋ ํด๋ผ์ด์ธํธ ์ธก ๋ฐ์ดํฐ ํจ์นญ์ ๋์์ค๋ค.
SSR์์๋ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์์ฑ๋ HTML์ ๋ณด๋ด์ฃผ๊ธฐ ๋๋ฌธ์ React Query์ ์บ์๋ฅผ ํด๋ผ์ด์ธํธ์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ํจ์นญํ๊ณ , ํด๋ผ์ด์ธํธ์์ React Query ์บ์๋ฅผ ๋ณต์ํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
SSR์์ React Query
1. dehydrate
์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ํจ์นญํ๊ณ , ์ด๋ฅผ ์ง๋ ฌํํ์ฌ ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌํ๋ ์ญํ ์ ํ๋ค.
2. HydrationBoundary
ํด๋ผ์ด์ธํธ ์ธก์์ ์๋ฒ๋ก๋ถํฐ ์ ๋ฌ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ React Query ์บ์์ ๋ณต์(hydration)ํ๋ ์ญํ ์ ํ๋ค.
๋์ ๊ณผ์
1. ์๋ฒ์์ ๋ฐ์ดํฐ ํจ์นญ
React Query์ `prefetchQuery`๋ฅผ ์ฌ์ฉํด ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์จ๋ค.
2. ํด๋ผ์ด์ธํธ๋ก ๋ฐ์ดํฐ ์ ์ก
์๋ฒ์์ ์ง๋ ฌํํ ๋ฐ์ดํฐ๋ฅผ `dehydrate`๋ฅผ ํตํด ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌํ๋ค.
3. ํด๋ผ์ด์ธํธ์์ ๋ฐ์ดํฐ ํ์ด๋๋ ์ด์
ํด๋ผ์ด์ธํธ๋ `HydrationBoundary`๋ฅผ ํตํด ์ ๋ฌ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ React Query ์บ์์ ์ ์ฅํ๊ณ ์ฌ์ฌ์ฉํ๋ค.
์์ ์ฝ๋
0. Provider ์ค์
// queryProvider.ts
'use client';
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { PropsWithChildren } from 'react';
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// ์์ ์ค์
staleTime: 60 * 1000,
refetchInterval: 60 * 1000,
},
},
});
}
let browserQueryClient: QueryClient | undefined;
function getQueryClient() {
if (isServer) return makeQueryClient();
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
function QueryProviders({ children }: PropsWithChildren) {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} client={queryClient} />
{children}
</QueryClientProvider>
);
}
export default QueryProviders;
makeQueryClient
์ญํ : ๋ฆฌ์กํธ ์ฟผ๋ฆฌ์ QueryClient ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
defaultOptions
์ ์ฌ์ฉํด ์ฟผ๋ฆฌ ๊ธฐ๋ณธ ๋์(์บ์ฑ, ๊ฐฑ์ ๋ฑ)์ ์ ์ํ๋ค.
getQueryClient
์ญํ : ์๋ฒ์ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์ ๋ง๊ฒ QueryClient๋ฅผ ๊ด๋ฆฌํ๋ค.
- ์๋ฒ ํ๊ฒฝ(
isServer
๊ฐtrue
)์ผ ๋, ํญ์ ์๋ก์ด QueryClient๋ฅผ ์์ฑํ๋ค. (์๋ฒ์์๋ ์์ฒญ ๊ฐ ์ํ๋ฅผ ๊ณต์ ํ๋ฉด ์ ๋๊ธฐ ๋๋ฌธ) - ๋ธ๋ผ์ฐ์ ํ๊ฒฝ(
isServer
๊ฐfalse
)์ผ ๋, ๋ธ๋ผ์ฐ์ ์์๋browserQueryClient
๋ผ๋ ์ ์ญ ๋ณ์์ ์ ์ฅํ์ฌ ์ฌ์ฌ์ฉํ๋ค. ๋์ผํ QueryClient๋ฅผ ์ฌ์ฌ์ฉํด ์บ์์ ์ํ๋ฅผ ๊ณต์ ํ ์ ์๋ค.
QueryProvisers
์ญํ : getQueryClient
๋ก ์ค์ ํ QueryClient๊ฐ์ฒด๋ฅผ Provider
๋ฅผ ํตํด ์ ์ญ์ ์ผ๋ก ์ฟผ๋ฆฌ ์ํ๋ฅผ ๊ณต์ ํ๋ค.
// layout.tsx
import QueryProviders from '@/utils/queryProvider';
const RootLayout = async ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return (
<html lang="ko">
<body>
<QueryProviders>
{children}
</QueryProviders>
</body>
</html>
);
};
export default RootLayout;
1. ๋ฐ์ดํฐ ํจ์นญ ํจ์
jsonplaceholder
๋ฅผ ์ด์ฉํ ๋๋ฏธ ๋ฐ์ดํฐ ํ์ฉ
export const fetchPosts = async (pageNum = 1) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${pageNum}`,
);
return response.json();
};
2. SSR์์์ ๋ฐ์ดํฐ ํจ์นญ๊ณผ Hydration
// page.tsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query';
import Posts from './Post';
const page = async () => {
const queryClient = new QueryClient();
// ์๋ฒ์์ ๋ฐ์ดํฐ ํจ์นญ
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<h1>์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง ํ
์คํธ</h1>
<div>----------------------</div>
<Posts />
</HydrationBoundary>
);
};
export default page;
HydrationBoundary
: ์๋ฒ์์ ๋ฏธ๋ฆฌ ํจ์นญํ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ด์ธํธ์์ ๋ณต์ํ์ฌ React Query ์บ์๋ก ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒ ํ๋ค.
3. ํด๋ผ์ด์ธํธ์์ ๋ฐ์ดํฐ ํ์ฉ
Posts
์ปดํฌ๋ํธ๋ React Query์ useQuery
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ด์ธํธ์์ ๋ถ๋ฌ์จ๋ค. ์ด๋ฏธ ํ์ด๋๋ ์ด์
๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ๊ฑฐ๋, ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ์๋ก ํจ์นญํ๋ค.
// Posts.tsx
'use client';
import { useQuery } from '@tanstack/react-query';
import { fetchPosts } from './page';
const Posts = () => {
console.log('Posts ์ปดํฌ๋ํธ');
const { data } = useQuery({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
});
return (
<div>
{data?.map((post) => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</div>
);
};
export default Posts;
React Query์ SSR์ ์ด์
- ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ค๊ธฐ: ํ์ด์ง ๋ก๋ฉ ์๋๊ฐ ๋นจ๋ผ์ง๊ณ SEO ์ต์ ํ๊ฐ ๊ฐ๋ฅํ๋ค.
- ํด๋ผ์ด์ธํธ์์ ๋ฐ์ดํฐ ๋ณต์: ์ด๋ฏธ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ๋ถํ์ํ ์ถ๊ฐ ๋คํธ์ํฌ ์์ฒญ์ ์ค์ธ๋ค.
- ์ ์ฐํ ๋ฐ์ดํฐ ๊ด๋ฆฌ: ์ ์ /๋์ ํ์ด์ง๋ฅผ ๋ชจ๋ ํจ๊ณผ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ค.
์ฐธ๊ณ ๋ก SSR๋ก ๋์ํ๋์ง ํ ์คํธ ํด๋ณด๊ณ ์ถ๋ค๋ฉด ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ๋๊ณ ๋ ๋๋งํ๋ฉด ๋๋ค.
Next.js์ ๋ด์ฅ ์บ์ฑ ๊ธฐ๋ฅ๊ณผ fetch์์์ ์กฐ๊ฑด๋ถ ์บ์ฑ(Conditional Request)์ ํตํด ๋ฐ์ดํฐ์ stale ์ํ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ฒ ๋๋ฉด์, ‘React Query๋ฅผ ๊ณ์ ์ฌ์ฉํด์ผ ํ ๊น?’๋ผ๋ ์๋ฌธ์ด ๋ค์๋ค.
React Query๊ฐ ์ ๊ณตํ๋ ์บ์ฑ, ์๋ฌ ํธ๋ค๋ง, ๋ฌดํ ์คํฌ๋กค ๋ฑ ๋ค์ํ ๊ธฐ๋ฅ์ด ๋งค๋ ฅ์ ์ด์๊ธฐ ๋๋ฌธ์ ๋ง์ด ์ฌ์ฉ๋์๋ค. ์ง๊ธ๋ React๋ก ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค๋ฉด React Query๋ฅผ ์ฌ์ฉํ ๊ฒ ๊ฐ๋ค. ํ์ง๋ง Next.js์ ๋ธ๋ผ์ฐ์ fetch API์ ๊ธฐ๋ฅ๋ค์ด ์ ์ ๋ ๋ฐ์ ํ๋ฉด์, ๋์ผํ ๊ธฐ๋ฅ์ ๋ ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์๋ ์ต์ ๋ค์ด ๋์ด๋๊ณ ์๋ค.
React Query์ ๊ธฐ๋ฅ์ ์ฌ์ ํ ๊ฐ๋ ฅํ๊ณ ์ ์ฉํ์ง๋ง, ์์ผ๋ก๋ ์ฌ๋ฌ๊ฐ์ง๋ฅผ ๊ณ ๋ คํด์ผ ํ ๊ฒ ๊ฐ๋ค.
์ฐธ๊ณ ์๋ฃ
https://soobing.github.io/react/server-rendering-and-react-query/
์๋ฒ์์ React Query prefetching ํ ๋ฐ์ดํฐ ์ฌ์ฉํ๊ธฐ
Next.js๋ Remix ๊ฐ์ ํ๋ ์์ํฌ ๋ด์์ React-Query๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ์๋ฒ ๋ ๋๋ง ๋ ๋ ์์ฒญ ํ ์๋ต๋ฐ์ ๋ฐ์ดํฐ๋ฅผ SPA ๋ฐฉ์์ผ๋ก ์ ํ๋๊ณ ๋์๋ ์ ์งํ ์ ์์๊น์? ์ด๋ป๊ฒ ๊ฐ๋ฅํ ๊น์? React Query์ hy
soobing.github.io
https://sonblog.vercel.app/blogs/blog/nextjs/react-query-ssr
React Query SSR
react query์์ ssr์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ ๋ฆฌํฉ๋๋ค.
sonblog.vercel.app
https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr
Advanced Server Rendering | TanStack Query React Docs
Welcome to the Advanced Server Rendering guide, where you will learn all about using React Query with streaming, Server Components and the Next.js app router. You might want to read the before this on...
tanstack.com