🗂
react-queryをサーバーサイドで働かさせて、データ取得の最適化をする
はじめに
以下は、Next.js 14x, Tanstack Query 5x を使った例となります。
セットアップ
app/query-client.ts
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 60 * 1000, //💡 サーバーサイドで毎回データ取得したくないので、0以上のstaleTimeを指定しましょう
},
},
});
}
let browserQueryClient: QueryClient | undefined = undefined;
function getQueryClient() {
if (isServer) {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
export default getQueryClient;
Provider コンポーネント
app/provider.tsx
"use client";
import Devtools from "@/src/components/ui/dev-tools";
import "@fontsource-variable/noto-sans-jp/";
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactNode } from "react";
import { getQueryClient } from "./query-client";
export default function Providers({ children }: { children: ReactNode }) {
const queryClient = getQueryClient(); //💡 インスタンスを取得します
return (
<QueryClientProvider client={queryClient}>
{children}
<Devtools />
</QueryClientProvider>
);
}
そのあと、layout コンポーネントなどに provider で children を囲むイメージ
app/layout.tsx
import Providers from "./provider";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang='en'>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
サーバーサイドでデータ取得セットアップ
サイトの情報を取得する react-query を内包したカスタムフックがあるとします。
app/_hooks/useSite.ts
import { getSiteInfo } from "@firebase/admin/getSiteInfo";
import { useQuery } from "@tanstack/react-query";
export const SITE_INFO_KEY = "siteInfo";
export default function useSite() {
const res = useQuery({
queryKey: [SITE_INFO_KEY],
queryFn: getSiteInfo,
meta: {
errorMessage: "サイト情報の取得に失敗しました",
},
});
return res;
}
サーバーサイドで react-query を働かさせます。
useSite フックが使われているクライアントコンポーネントを囲むサーバーコンポーネントにて、
HydrationBoundary
を設定。
(cms)/layout.tsx
import AppBar from "@/src/components/component/page-app-bar";
import { getSiteInfo } from "@/src/services/firebase/admin/getSiteInfo";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import { Provider } from "./provider";
import { SITE_INFO_KEY } from "../_hooks/useSite";
export default async function CMSLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const queryClient = new QueryClient();
await queryClient.prefetchQuery({ //prefetchQueryを使います
queryKey: [SITE_INFO_KEY],//💡 カスタムフックと同値のqueryキーを指定します
queryFn: getSiteInfo, //💡 同様のquery関数も指定
});
//💡 HydrationBoundary以降すべてのuseSiteフックが行うfetchは、サーバーサイドで行われます
return (
<>
<HydrationBoundary state={dehydrate(queryClient)}>
<div className='overflow-auto pb-16'>{children}</div>
</HydrationBoundary>
<AppBar />
</>
);
}
結果
ASIS
本来では、以下のように、コンポーネントが hydrate されてから、クライアントでデータ取得を行うようにしています。
サーバーから返ってきた bundle にも含まれません
TOBE
一方、ネットワークレスポンスを見ると、予め取得したデータがコンポーネントファイルの bundle と一緒に返ってきていることがわかります。
useSite のフックが行うクライアントサイド fetch もしません。
あと圧倒的にデータの描画も早くなりました 👏
ぜひ試してみてください。
Discussion