TanStack Query
@lazarv/react-serverは、TanStack Query(旧React Query)と連携して、サーバーサイドプリフェッチとクライアントサイドハイドレーションによる強力なデータフェッチを提供します。サーバーコンポーネントでクエリをプリフェッチし、クライアントでシームレスにデータをハイドレートすることで、不要な再フェッチを回避できます。
TanStack Queryをプロジェクトにインストールします:
pnpm add @tanstack/react-query
TanStack Queryを使用するには、QueryClientを作成し、アプリをQueryClientProviderでラップする必要があります。QueryClientProviderはReactコンテキストに依存するため、クライアントコンポーネントである必要があります。
サーバーとブラウザの両方の環境を処理するクエリクライアントファクトリを作成します:
app/get-query-client.jsximport {
defaultShouldDehydrateQuery,
isServer,
QueryClient,
} from "@tanstack/react-query";
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// SSRでは、クライアントでの即座の再フェッチを避けるために
// デフォルトのstaleTimeを0より大きく設定します
staleTime: 60 * 1000,
},
dehydrate: {
// 保留中のクエリをデハイドレーションに含める
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === "pending",
},
},
});
}
let browserQueryClient = undefined;
export function getQueryClient() {
if (isServer) {
// サーバー: 常に新しいクエリクライアントを作成
return makeQueryClient();
} else {
// ブラウザ: まだない場合は新しいクエリクライアントを作成
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
アプリの残りの部分にQueryClientを提供するクライアントコンポーネントを作成します:
app/providers.jsx"use client";
import {
isServer,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
});
}
let browserQueryClient = undefined;
function getQueryClient() {
if (isServer) {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
export default function Providers({ children }) {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
次に、ルートレイアウトでProvidersコンポーネントでアプリをラップします:
app/layout.jsximport Providers from "./providers";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<head />
<body suppressHydrationWarning>
<Providers>{children}</Providers>
</body>
</html>
);
}
@lazarv/react-serverでTanStack Queryを使用する主な利点は、サーバーコンポーネントでデータをプリフェッチし、クライアントでハイドレートできることです。これにより、ローディング状態なしでデータが即座に利用可能になります。
サーバーコンポーネントで、クエリクライアントを使用してデータをプリフェッチし、クライアントコンポーネントをHydrationBoundaryでラップします:
app/page.jsximport { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { getPosts } from "./get-posts";
import { getQueryClient } from "./get-query-client";
import Posts from "./posts";
export default function PostsPage() {
const queryClient = getQueryClient();
queryClient.prefetchQuery({
queryKey: ["posts"],
queryFn: getPosts,
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
);
}
クライアントコンポーネントでは、同じクエリキーでuseSuspenseQuery(またはuseQuery)を使用できます。サーバーでデータがプリフェッチされている場合、ローディング状態なしで即座に利用可能になります:
app/posts.jsx"use client";
import { useSuspenseQuery } from "@tanstack/react-query";
import { getPosts } from "./get-posts";
export default function Posts() {
const { data } = useSuspenseQuery({
queryKey: ["posts"],
queryFn: getPosts,
});
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
サーバーとクライアント間でデータフェッチロジックを共有するために、ランタイム環境を検出する同型データフェッチャーを作成できます:
app/get-posts.mjsexport async function getPosts() {
if (typeof document === "undefined") {
// サーバー: データを直接インポート
const { default: posts } = await import("../data/posts.json");
return posts;
} else {
// クライアント: APIルートからフェッチ
const res = await fetch("/api/posts");
return res.json();
}
}
ファイルシステムルーティングを使用したAPIルートと組み合わせることができます:
app/api/GET.posts.jsximport posts from "../../data/posts.json";
export default async function GET() {
return new Response(JSON.stringify(posts), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
異なるデータセットをプリフェッチするために、別々のサーバーコンポーネントにHydrationBoundaryコンポーネントをネストできます。これは複数のデータ依存関係を構成するのに便利です:
app/comments-server.jsximport { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import Comments from "./comments";
import { getComments } from "./get-comments";
import { getQueryClient } from "./get-query-client";
export default function CommentsServerComponent() {
const queryClient = getQueryClient();
queryClient.prefetchQuery({
queryKey: ["comments"],
queryFn: getComments,
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Comments />
</HydrationBoundary>
);
}
@lazarv/react-serverでTanStack Queryを使用する完全な例については、TanStack Query exampleを確認してください。