๋ณธ ๋ฌธ์์์๋, Next.js 12์์ React-query ๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ณผ ๊ฒ์ด๋ค.
ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์ ์ฌ์ฉํ๋์ง, ์๋ฒ ์ฌ์ด๋์์ ์ฌ์ฉํ๋์ง์ ๋ฐ๋ผ์ ๊ตฌ์ฒด์ ์ธ ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ๋์ ๋ฐฉ์์ ์ฐจ์ด๊ฐ ์๋ค.
https://tanstack.com/query/v4/docs/framework/react/guides/ssr
๐น 1. ํด๋ผ์ด์ธํธ์์๋ง React Query ์ฌ์ฉ (๊ธฐ๋ณธ)
// pages/users.tsx
import { useQuery } from '@tanstack/react-query';
export default function Users() {
const { data, isLoading } = useQuery(['users'], fetchUsers);
...
}
- useQuery๋ ํด๋ผ์ด์ธํธ์์ ์๋ → CSR ๊ธฐ๋ฐ
- ์๋ฒ๋ ๋จ์ํ HTML ์ ธ์ ๋ ๋ํ๊ณ , ๋ฐ์ดํฐ๋ ํด๋ผ์ด์ธํธ์์ fetch
โก๏ธ ์ฅ์ : UX ๋น ๋ฆ, ์บ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
โก๏ธ ๋จ์ : SEO ๋ฐ ์ด๊ธฐ LCP ์๋ ํ๊ณ
๐น 2. SSR/SSG + React Query ์ฌ์ฉ (Hydration ๋ฐฉ์)
SSR์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ fetch → React Query๋ก Hydrateํ๋ ๋ฐฉ์
// pages/users.tsx
import { QueryClient, dehydrate } from '@tanstack/react-query';
export async function getServerSideProps() {
const queryClient = new QueryClient();
await queryClient.prefetchQuery(['users'], fetchUsers);
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}
<Hydrate state={pageProps.dehydratedState}>
<QueryComponent />
</Hydrate>
โก๏ธ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ถ๋ฌ์ค๊ณ HTML์ ํฌํจ → SEO, LCP, FCP ์ฑ๋ฅ ํฅ์
โก๏ธ ํด๋ผ์ด์ธํธ์์๋ ์ด๋ฏธ ์บ์๋ ๋ฐ์ดํฐ๋ก ์ฆ์ ๋ ๋๋ง
๋จ์ SSR๋ง ์ฌ์ฉํ๋ ๊ฒ๊ณผ SSR/SSG + React Query ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ ์ฌ์ด์ ๋ฌด์จ ์ฐจ์ด๊ฐ ์๋์ง ๊ถ๊ธํ ์ ์๋ค.
1. ๋จ์ SSR๋ง ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
- ์๋ฒ์์ getServerSideProps ๊ฐ์ ํจ์๋ก ๋ฐ์ดํฐ๋ฅผ fetchํ๊ณ , HTML์ ํฌํจ์์ผ์ ํด๋ผ์ด์ธํธ๋ก ๋ณด๋
- ํด๋ผ์ด์ธํธ๋ ์ด๋ฏธ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋ HTML์ ๋ฐ์ ๋ ๋๋งํ๋ฏ๋ก, ์ด๊ธฐ ๋ก๋ฉ์ด ๋น ๋ฆ
- ํ์ง๋ง ์ดํ, ํด๋ผ์ด์ธํธ ์ชฝ์ ์ํ ๊ด๋ฆฌ๋ ์บ์ฑ์ ๋ฐ๋ก ๊ตฌํํด์ค์ผ ํจ
- ํ์ด์ง ์ด๋ ์์๋ ๋ค์ SSR์ด ์ผ์ด๋๊ธฐ ๋๋ฌธ์ ๋คํธ์ํฌ ๋น์ฉ์ด ๋ฐ๋ณต๋จ
๐ธ ๋จ์ : ๋ฐ์ดํฐ ์ฌํ์ฉ ๋ถ๊ฐ, ํด๋ผ์ด์ธํธ ์ํ ๊ด๋ฆฌ ๋ถ์กฑ, ์ถ๊ฐ API ํธ์ถ ๋ฐ์ ๊ฐ๋ฅ์ฑ
2. SSR/SSG + React Query ์ฌ์ฉ
- ์๋ฒ์์ React Query๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ fetchํ๊ณ , dehydrate()๋ก ์ด๊ธฐ ์บ์ ์ํ๋ฅผ HTML์ ์ฃผ์
- ํด๋ผ์ด์ธํธ๋ hydrate()๋ก ์บ์๋ ๋ฐ์ดํฐ๋ก ๋ฐ๋ก ๋ ๋๋ง, ์ถ๊ฐ API ์์ฒญ ์์ด๋ ๋ฐ๋ก ์ธํฐ๋์ ๊ฐ๋ฅ
- ํ์ด์ง ์ด๋ ์์๋ React Query ์บ์๋ฅผ ์ฌํ์ฉ, ๋ถํ์ํ refetch ๋ฐฉ์ง
- staleTime, cacheTime ๋ฑ์ ์ค์ ํด ๋ฐ์ดํฐ freshness๋ ์กฐ์ ๊ฐ๋ฅ
โ ์ฅ์ : CSR์์๋ ๋ถ๋๋ฝ๊ฒ ๋์, ๋ฐ์ดํฐ ์ค๋ณต ์์ฒญ ์์, ์ด๊ธฐ ๋ก๋ฉ ์๋ + UX + ์บ์ฑ ์ ๋ต ์ต์ ํ
SSR/SSG + React Query ๊ตฌํ ๋ฐฉ์
https://tanstack.com/query/latest/docs/framework/react/guides/ssr#server-rendering--react-query
Server Rendering & Hydration | TanStack Query React Docs
In this guide you'll learn how to use React Query with server rendering. See the guide on for some background. You might also want to check out the before that. For advanced server rendering patterns,...
tanstack.com
์ด๋ฅผ ๊ตฌํํ๋ ๋ฐฉ์์๋ ํฌ๊ฒ ๋๊ฐ์ง๊ฐ ์๋ค.
1๏ธโฃ initialData ์ฌ์ฉ
์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ fetch ํ ๋ค์ ํด๋น props ๋ฅผ useQuery() ํจ์์ initialData ์ต์ ์ผ๋ก ์์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๊ฒ
export async function getServerSideProps() {
const posts = await getPosts()
return { props: { posts } }
}
function Posts(props) {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: getPosts,
initialData: props.posts,
})
// ...
}
setup์ด ๊ฐ๋จํ์ง๋ง ๋ช๊ฐ์ง tradeoff ๊ฐ ์๋ค.
1. ํ์ ํธ๋ฆฌ์์ useQuery ํธ์ถ ์, initialData๋ ํจ๊ป ์ ๋ฌํด์ผ ํจ
- ์ปดํฌ๋ํธ ํธ๋ฆฌ ๊น์ํ ๊ณณ์์ useQuery๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, ํด๋น ์์น๊น์ง initialData๋ฅผ ๊ณ์ props๋ก ๋ด๋ ค์ค์ผ ํด์ ๋ฒ๊ฑฐ๋กญ๊ณ ๋ณต์กํจ.
2. ์ฌ๋ฌ ๊ณณ์์ ๋์ผ ์ฟผ๋ฆฌ๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ, ํ ๊ณณ์๋ง initialData ์ ๋ฌํ๋ฉด ์ํ
- ํ๋์ ์ปดํฌ๋ํธ๋ง initialData๋ฅผ ์ค์ ํ๊ณ ๋ค๋ฅธ ๊ณณ์ ์ค์ ํ์ง ์์ผ๋ฉด, ํด๋น ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ผ์ง ๋ ๋๋จธ์ง ์ปดํฌ๋ํธ๋ค์ด ๋ฐ์ดํฐ๋ฅผ ๋ชป ๋ฐ๊ฒ ๋ ์ ์์.
- ์ฆ, ์ปดํฌ๋ํธ ์ฌ๋ฐฐ์น/๋ฆฌํฉํฐ๋ง ์ ์ทจ์ฝํด์ง.
3. SSR์์ initialData๋ ์ ํํ fetch ์์ ์ ๋ชจ๋ฆ
- initialData๋ ์ธ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์๋์ง๋ฅผ ๊ธฐ๋กํ์ง ์์ (dataUpdatedAt ์์).
- ๊ทธ๋์ ํ์ด์ง๊ฐ ์ค๋ ์ด๋ ค ์์ด๋ "์ธ์ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋๋์ง" ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์, ๋ฆฌํจ์นญ ํ๋จ์ด ์ด๋ ต๊ณ ๋ถ์์ ํจ.
4. ์บ์์ ์ด๋ฏธ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด, initialData๋ ๋ฎ์ด์ฐ๊ธฐ ์ ๋จ
- ๋ง์ฝ ์บ์์ ์ด์ ๋ฐ์ดํฐ๊ฐ ์๊ณ , ์๋ก์ด initialData๊ฐ ์ค์ ๋ก ๋ ์ต์ ์ด๋๋ผ๋ ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ ์ ์งํจ.
- ํนํ getServerSideProps ๊ฐ์ ๊ฒฝ์ฐ, ํ์ด์ง๋ฅผ ์๋ค๊ฐ๋ค ํ ๋ ๋งค๋ฒ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค์ง๋ง, ์ด๊ธฐ ๋ฐ์ดํฐ๋ก๋ง ์บ์๊ฐ ์ ์ง๋๋ฏ๋ก ๊ฐฑ์ ๋์ง ์์.
=> ์ฆ, ์ ํํ๊ณ ์ผ๊ด๋ ๋ฐ์ดํฐ ์ด๊ธฐํ์ ์ง์ฅ์ ๋ฐ์
2๏ธโฃ Hydration ์ฌ์ฉ
์๋ฒ์์ query๋ฅผ prefetchํ๊ณ , ์บ์๋ฅผ dehydrateํ๊ณ , client์์ rehydrate ํ๋ค. ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋์์ ๋์ผํ QueryClient ์ํ๋ก ์์ํ์ฌ, SSR๋ก ์์ฑ๋ HTML๊ณผ ํด๋ผ์ด์ธํธ์ ์ด๊ธฐ ๋งํฌ์ ์ ์ผ์น์ํค๊ณ , stale ์บ์ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
๐ ๋จ๊ณ๋ณ ์ค๋ช
1. Preloading ๋จ๊ณ (์๋ฒ ์ธก)
- QueryClient๋ฅผ ์์ฑ:
const queryClient = new QueryClient();
- ํ์ํ ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ prefetch:
await Promise.all([
queryClient.prefetchQuery(['todos'], fetchTodos),
queryClient.prefetchQuery(['user', userId], fetchUser),
]);
- dehydrate(queryClient)๋ก ์ง๋ ฌํํ์ฌ ํด๋ผ์ด์ธํธ์ ์ ๋ฌ:
(์: getServerSideProps, loader, getStaticProps ๋ฑ)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
๐ง dehydrate()๋?
dehydrate(queryClient)๋ React Query์ ์บ์ ์ํ(queryClient.getQueryCache())๋ฅผ ์ง๋ ฌํ ๊ฐ๋ฅํ ๊ฐ์ฒด๋ก ๋ณํํ๋ ํจ์์ ๋๋ค.
- "dehydrate" = ๋ง ๊ทธ๋๋ก "์๋ถ์ ์ ๊ฑฐํ๋ค" → ํ์ํ ์ ๋ณด๋ง ์ถ๋ ค๋ธ ์์ฝ ๋ฐ์ดํฐ
- React Query์ ๋ณต์กํ ๋ด๋ถ ์ํ๋ฅผ JSON์ฒ๋ผ ํด๋ผ์ด์ธํธ์ ์ ๋ฌํ ์ ์๋ ๋จ์ํ ํํ๋ก ๋ง๋๋ ๊ฒ
2. ๋ ๋๋ง ๋จ๊ณ (์๋ฒ + ํด๋ผ์ด์ธํธ)
- HydrationBoundary๋ก ๋ํํ์ฌ ์ํ๋ฅผ ๋ณต์:
<HydrationBoundary state={dehydratedState}> <App /> </HydrationBoundary>
- dehydratedState๋ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ๊ฐ์ QueryClient์ ์ด๊ธฐ๊ฐ์ ๊ณต๊ธ.
๐ ์ ์ด๊ฑธ ํด์ผ ํ๋์?
React Query๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํด๋ผ์ด์ธํธ์์ ๋ฐ์ดํฐ๋ฅผ fetchํฉ๋๋ค.
ํ์ง๋ง SSR์์๋ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ์์ค๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์ธํธ๋ก ๋๊ฒจ์ค ์ ์์ด์ผ ํ์ฃ .
๊ทธ๋์:
- ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ fetch → queryClient์ ์ ์ฅ๋จ
- ๊ทธ queryClient๋ฅผ dehydrate() ํด์ JSON์ผ๋ก ๋ง๋ค๊ณ
- ๊ทธ๊ฑธ ํด๋ผ์ด์ธํธ์ props๋ก ์ ๋ฌ
- ํด๋ผ์ด์ธํธ๋ <HydrationBoundary state={dehydratedState}>๋ก ์ด ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ณต์(hydrate)
์ด๋ ๊ฒ ํ๋ฉด ์ด๊ธฐ ํ๋ฉด ๋ ๋๋ง ์์ ์์ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๊ฒ ๋ฉ๋๋ค.
๐ ์ด ๋ฐฉ์์ ์ฅ์
๋ฐ์ดํฐ ์ผ๊ด์ฑ | ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ๋์ผํ ์บ์ ์ํ์์ ์์ํ์ฌ, hydration mismatch ๋ฐฉ์ง |
Refetch ํ์ด๋ฐ ๋ช ํ | dataUpdatedAt์ด ๊ธฐ๋ก๋๋ฏ๋ก, ์ค๋๋ ๋ฐ์ดํฐ๋ง refetch๋จ |
๋ถํ์ํ API ํธ์ถ ์ค์ | ์ด๊ธฐ ์บ์๊ฐ ์์ด์ ํด๋ผ์ด์ธํธ์์ ๋ฐ๋ก refetch ์ ํด๋ ๋จ |
์ ์ฐ์ฑ | ์ผ๋ถ๋ prefetchํ๊ณ , ๋๋จธ์ง๋ ํด๋ผ์ด์ธํธ์์๋ง fetch ๊ฐ๋ฅ → ํผํฌ๋จผ์ค ์ต์ ํ ๊ฐ๋ฅ |
โ ๏ธ ์ฃผ์ํ ์
- initialData์ ๋ฌ๋ฆฌ dehydratedState๋ QueryClient ๋ด๋ถ์ ๋ชจ๋ ์ฟผ๋ฆฌ ์ํ๋ฅผ ์ง๋ ฌํํจ.
- getServerSideProps / getStaticProps ์ธ์๋, Remix, RSC ๋ฑ ๊ฐ ํ๋ ์์ํฌ๋ง๋ค ์ ์ฉ ๋ฐฉ์์ด ์ฝ๊ฐ์ฉ ๋ค๋ฆ.
- queryClient๋ ์๋ฒ prefetch์ฉ / ์๋ฒ ๋ ๋๋ง์ฉ / ํด๋ผ์ด์ธํธ์ฉ ์ด 3๊ฐ๊ฐ ์ฌ์ฉ๋จ:
- ์๋ฒ prefetch ์ฉ๋ (loader ๋ด๋ถ)
- SSR HTML ์์ฑ์ฉ
- ํด๋ผ์ด์ธํธ hydration ์ฉ๋
๐ง ๊ธฐ์ตํ ์
initialData๋ ์์ ์๊ธ์ฒ์น,
dehydratedState + HydrationBoundary๋ ์ ํํ๊ณ ํ์ฅ ๊ฐ๋ฅํ ๊ณต์ ํด๊ฒฐ์ฑ ์ ๋๋ค.
'ํ๋ก ํธ์๋ > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Redux ์์ ๋น๋๊ธฐ ์ํ ๋ณ๊ฒฝ ์ฒ๋ฆฌ์ Redux-thunk ๋ฅผ ์ด์ฉํด์ผํ๋ ์ด์ (0) | 2024.10.31 |
---|