框架
版本

TanStack Query 集成

重要

此集成自动化了 TanStack Router 和 TanStack Query 之间的 SSR 脱水/水合和流式传输。如果你还没有阅读标准的外部数据加载指南,请从那里开始。

你将获得什么

  • 你的 QueryClient自动 SSR 脱水/水合
  • 在初始服务器渲染期间解析的查询流式传输到客户端
  • 对从查询/突变抛出的 redirect() 进行重定向处理
  • 使用 QueryClientProvider 进行可选的提供者包装

安装

TanStack Query 集成是一个独立的包,你需要安装它。

sh
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

tsx
// 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 并保留你的自定义包装器。

SSR 行为与流式传输

  • 在服务器渲染期间,集成会脱水初始查询,并流式传输在渲染过程中解析的任何后续查询。
  • 在客户端,集成会水合初始状态,然后增量水合流式传输的查询。
  • 来自 useSuspenseQuery 或加载器预取的查询会参与 SSR/流式传输。普通的 useQuery 不会在服务器上执行。

在路由中使用

使用 useSuspenseQuery 与 useQuery

  • useSuspenseQuery:在 SSR 期间,当需要数据时在服务器上运行,并在解析后流式传输到客户端。
  • useQuery:不会在服务器上执行;它会在水合后在客户端获取。将其用于 SSR 不需要的数据。
tsx
// 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)

通过加载器预加载,通过 Hook 读取

在路由 loader 中预加载关键数据,以避免瀑布效应和加载闪烁,然后在组件中读取它。集成确保服务器获取的数据在 SSR 期间脱水并流式传输到客户端。

tsx
// 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>
}

预取与流式传输

你也可以在加载器中使用 fetchQueryensureQueryData 进行预取,而无需在组件中消费数据。如果你直接从加载器返回 Promise,它将被等待,从而阻塞 SSR 请求,直到查询完成。如果你不等待 Promise 也不返回它,查询将在服务器上启动,并将在不阻塞 SSR 请求的情况下流式传输到客户端。

tsx
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(...),集成会在客户端拦截它并执行路由器导航。

  • 默认启用
  • 如果你需要自定义处理,请使用 handleRedirects: false 禁用

与 TanStack Start 配合使用

TanStack Start 底层使用 TanStack Router。设置相同,集成将自动在 SSR 期间流式传输查询结果。

我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。