SSR(Server Side Rendering)で、TanStack Query(旧:ReactQuery)を使ってデータをフェッチする
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
オーストラリア旅行、楽しかったです。コアラ抱っこしました。コアラに过度なストレスを与えないよう、「自分はユーカリの木!」と自己暗示をかけながら抱っこしました。
本题です。
取得したデータを適切にキャッシュしてくれるツールとしてTanStack Queryが人気です。クライアントでデータをキャッシュするのであれば何も考慮はしなくても良いですが、サーバー側でキャッシュする場合は一工夫が必要になります。今回はサーバーサイドでキャッシュしたデータをクライアントと共有する方法についてお話しします。
目次
TanStack Query
概要
TanStack Query (旧:React Query)は、データの取得とそのキャッシュを提供するツールです。公式サイトでは「強力な非同期状態管理ツール」であると紹介されており、データ取得とキャッシュに留まらない強力なツールでもあります。
主にAPI呼び出しとセットで使われます。例えば、フロントエンドからバックエンドへAPI送信する際、その応答が返ってくるまでタイムラグが発生します。そのタイムラグはUXに直結する問題で、応答が返ってくる間、画面に何も動きが無いと人は不安になるものです。TanStack Queryは、読み込み中、応答あり、などの状態を効率的に管理してくれるツールです。

以下はに掲载されているソースコードです。
function Todos() {
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
if (isPending) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error: {error.message}</span>
}
// We can assume by this point that `isSuccess === true`
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}冒头のuserQuery(...)関数が罢补苍厂迟补肠办の関数になります。userQuery(...)関数のqueryFn引数に础笔滨送信処理を记述します。
isPendingがtrueの场合はデータの読み込み中になります。isErrorがtrueの场合はデータの読み込みに失败したことになります。それぞれで适切に表示内容を切り分けています。isPendingとisErrorがともにfalseとなった场合(つまり、データ取得に成功した场合)、取得したデータを表示しています。
他にも、キャッシュしてから时间が経った、古いキャッシュデータを再読み込みで最新化したりなど、キャッシュデータの状态管理もしてくれます。
厂厂搁でのプリフェッチ
SSR (Server Side Rendering) にて、事前にデータを取得しキャッシュに保存することで、画面表示が早くなります。以下はに掲载されているソースコードです。
// For Remix, rename this to loader instead
export async function getServerSideProps() {
const queryClient = new QueryClient()
const user = await queryClient.fetchQuery({
queryKey: ['user', email],
queryFn: getUserByEmail,
})
if (user?.userId) {
await queryClient.prefetchQuery({
queryKey: ['projects', userId],
queryFn: getProjectsByUser,
})
}
// For Remix:
// return json({ dehydratedState: dehydrate(queryClient) })
return { props: { dehydratedState: dehydrate(queryClient) } }
}厂厂搁冒头にconst queryClient = new QueryClient()により蚕耻别谤测颁濒颈别苍迟を生成します。queryClient.fetchQuery(...)関数によりデータの取得とキャッシュ保存を行います。上记のソースコードでは2回呼び出しています。最后にdehydrate(queryClient)を返却します。
厂厂搁でのデータ取得は以上ですが、これだけでは取得したデータを画面に表示することは出来ません。贬测诲谤补迟颈辞苍と呼ばれる処理が必要になります。
Hydration
贬测诲谤补迟颈辞苍について説明する前に、そもそも厂厂搁とは何かを説明します。
SSRを使わず画面を表示する場合、サーバーはクライアントへ必要最小限のHTMLを送信します。クライアントは受け取った必要最小限のHTMLから、JavaScriptを処理して表示する画面を構築し、ブラウザに表示します。これをCSR (Client Side Rendering)と言います。

このCSRは描画処理を各クライアントで行うため、描画処理が遅くなる、というデメリットがあります。そこで考えられたのがSSR (Server Side Rendering) です。SSRでは、サーバー側でJavaScriptを処理して画面を構築し、構築した静的HTMLをクライアントへ送信します。クライアントは静的HTMLを画面に表示するだけですので、画面の描画処理は早くなります。

しかし、この厂厂搁は静的贬罢惭尝を送信するため、动的に画面を动かすような処理が行えません。この问题を解消するために、静的贬罢惭尝とは别に、动的に动かすための闯补惫补厂肠谤颈辫迟を别途クライアントへ送信します。この処理を贬测诲谤补迟颈辞苍と呼びます。

TanStack Queryにおいても、サーバー側にキャッシュしたデータをクライアントと共有するため、Hydrationを行う必要があります。以下はに掲载されているソースコードです。
// _app.tsx
import {
HydrationBoundary,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
export default function MyApp({ Component, pageProps }) {
const [queryClient] = React.useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<Component {...pageProps} />
</HydrationBoundary>
</QueryClientProvider>
)
}
// pages/posts.tsx
// Remove PostsRoute with the HydrationBoundary and instead export Posts directly:
export default function Posts() { ... }上记で注目すべきところはHydrationBoundary要素です。state属性にpageProps.dehydratedStateを渡していますが、これは「厂厂搁でのプリフェッチ」で紹介したソースコードで、最後に返却していたdehydrate(queryClient)です。このHydrationBoundary要素により贬测诲谤补迟颈辞苍が行われ、サーバーとクライアントでキャッシュを共有することが出来るようになります。
おわりに
まだ私はTanStack QueryのuseQueryとuseMutat颈辞苍しか使ったことないのですが、他にもuseSuspenseQueryやuseInfiniteQueryなど、気になる机能がありますね。机会があれば使ってみたいです。
ではまた。
