重要
此集成自动化了 TanStack Router 和 TanStack Query 之间的 SSR 脱水/水合和流式传输。如果你还没有阅读标准的外部数据加载指南,请从那里开始。
TanStack Query 集成是一个独立的包,你需要安装它。
npm install -D @tanstack/react-router-ssr-query
# or
pnpm add -D @tanstack/react-router-ssr-query
# or
yarn add -D @tanstack/react-router-ssr-query
# or
bun add -D @tanstack/react-router-ssr-query
npm install -D @tanstack/react-router-ssr-query
# or
pnpm add -D @tanstack/react-router-ssr-query
# or
yarn add -D @tanstack/react-router-ssr-query
# or
bun add -D @tanstack/react-router-ssr-query
创建你的路由器并连接集成。确保在 SSR 环境中为每个请求创建新的 QueryClient。
// src/router.tsx
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'
export function createAppRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
// optionally expose the QueryClient via router context
context: { queryClient },
scrollRestoration: true,
defaultPreload: 'intent',
})
setupRouterSsrQueryIntegration({
router,
queryClient,
// optional:
// handleRedirects: true,
// wrapQueryClient: true,
})
return router
}
// src/router.tsx
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'
export function createAppRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
// optionally expose the QueryClient via router context
context: { queryClient },
scrollRestoration: true,
defaultPreload: 'intent',
})
setupRouterSsrQueryIntegration({
router,
queryClient,
// optional:
// handleRedirects: true,
// wrapQueryClient: true,
})
return router
}
默认情况下,集成会将你的路由器包装在一个 QueryClientProvider 中。如果你已经提供了自己的提供者,请传入 wrapQueryClient: false 并保留你的自定义包装器。
// Suspense: executes on server and streams
const { data } = useSuspenseQuery(postsQuery)
// Non-suspense: executes only on client
const { data, isLoading } = useQuery(postsQuery)
// Suspense: executes on server and streams
const { data } = useSuspenseQuery(postsQuery)
// Non-suspense: executes only on client
const { data, isLoading } = useQuery(postsQuery)
在路由 loader 中预加载关键数据,以避免瀑布效应和加载闪烁,然后在组件中读取它。集成确保服务器获取的数据在 SSR 期间脱水并流式传输到客户端。
// src/routes/posts.tsx
import { queryOptions, useSuspenseQuery, useQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
const postsQuery = queryOptions({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then((r) => r.json()),
})
export const Route = createFileRoute('/posts')({
// Ensure the data is in the cache before render
loader: ({ context }) => context.queryClient.ensureQueryData(postsQuery),
component: PostsPage,
})
function PostsPage() {
// Prefer suspense for best SSR + streaming behavior
const { data } = useSuspenseQuery(postsQuery)
return <div>{data.map((p: any) => p.title).join(', ')}</div>
}
// src/routes/posts.tsx
import { queryOptions, useSuspenseQuery, useQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
const postsQuery = queryOptions({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then((r) => r.json()),
})
export const Route = createFileRoute('/posts')({
// Ensure the data is in the cache before render
loader: ({ context }) => context.queryClient.ensureQueryData(postsQuery),
component: PostsPage,
})
function PostsPage() {
// Prefer suspense for best SSR + streaming behavior
const { data } = useSuspenseQuery(postsQuery)
return <div>{data.map((p: any) => p.title).join(', ')}</div>
}
你也可以在加载器中使用 fetchQuery 或 ensureQueryData 进行预取,而无需在组件中消费数据。如果你直接从加载器返回 Promise,它将被等待,从而阻塞 SSR 请求,直到查询完成。如果你不等待 Promise 也不返回它,查询将在服务器上启动,并将在不阻塞 SSR 请求的情况下流式传输到客户端。
import { createFileRoute } from '@tanstack/react-router'
import { queryOptions, useQuery } from '@tanstack/react-query'
const userQuery = (id: string) =>
queryOptions({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then((r) => r.json()),
})
export const Route = createFileRoute('/user/$id')({
loader: ({ params }) => {
// do not await this nor return the promise, just kick off the query to stream it to the client
context.queryClient.fetchQuery(userQuery(params.id))
},
})
import { createFileRoute } from '@tanstack/react-router'
import { queryOptions, useQuery } from '@tanstack/react-query'
const userQuery = (id: string) =>
queryOptions({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then((r) => r.json()),
})
export const Route = createFileRoute('/user/$id')({
loader: ({ params }) => {
// do not await this nor return the promise, just kick off the query to stream it to the client
context.queryClient.fetchQuery(userQuery(params.id))
},
})
如果查询或突变抛出 redirect(...),集成会在客户端拦截它并执行路由器导航。
TanStack Start 底层使用 TanStack Router。设置相同,集成将自动在 SSR 期间流式传输查询结果。
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。