高级服务器渲染 | 使用 Next.js 应用路由器进行 React 查询

2025-06-10

高级服务器渲染 | 使用 Next.js 应用路由器进行 React 查询

在本指南中,您将学习如何使用 React Query 进行服务器渲染。

什么是服务器渲染?

服务器渲染是指在服务器上生成初始 HTML,以便用户在页面加载时立即看到一些内容。这可以通过两种方式实现:

服务器端渲染 (SSR):每次请求页面时在服务器上生成 HTML。

静态站点生成 (SSG):在构建时预生成 HTML 或使用以前请求的缓存版本。

为什么服务器渲染有用?

使用客户端渲染,该过程如下所示:

  1. 加载不带内容的标记。
  2. 加载 JavaScript。
  3. 使用查询获取数据。

这需要至少三次服务器往返,用户才能看到任何内容。

服务器渲染简化了这个过程:

  1. 加载带有内容和初始数据的标记。
  2. 加载 JavaScript。

步骤 1 完成后,用户立即看到内容,步骤 2 完成后页面变为可交互的。初始数据已包含在标记中,因此最初不需要额外提取数据!

这与 React Query 有何关系?

使用 React Query,你可以在服务端生成/渲染标记之前预取数据,然后在客户端使用这些数据,避免再次进行数据获取。
现在来看看如何实现这些步骤……

初始设置

使用 React Query 的第一步始终是创建一个 queryClient 并将应用程序包装在一个 中<QueryClientProvider>。进行服务器渲染时,务必在应用内部以 React 状态创建 queryClient 实例(实例引用也可以)。这确保了数据不会在不同用户和请求之间共享,同时在每个组件生命周期中仅创建一次 queryClient。

商店.tsx:

"use client";
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export default function Store({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: {
          queries: {
            staleTime: 5 * 60 * 1000,
          },
        },
      }),
  );
  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

布局.tsx:

import Store from "@/provider/store";

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Store>{children}</Store>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

使用 Hydration API

只需稍加设置,您就可以使用queryClient在预加载阶段预获取查询,并将该 queryClient 的序列化版本传递给应用的渲染部分,并在那里重复使用。这样就避免了前面提到的缺点。

hydration.tsx :

import {
  QueryClient,
  dehydrate,
  HydrationBoundary,
} from "@tanstack/react-query";
import getData from "@/api/getData";
import React from "react";
export default async function Hydration({
  children,
}: {
  children: React.ReactNode;
}) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 5 * 60 * 1000, // this sets the cache time to 5 minutes
      },
    },
  });
  await Promise.all([
    queryClient.prefetchQuery({
      queryKey: ["profiles", "user"],
      queryFn: () => getData("profiles"),
    }),
    queryClient.prefetchQuery({
      queryKey: ["permissions", "user"],
      queryFn: () => getData("permissions"),
    }),
  ]);
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      {children}
    </HydrationBoundary>
  );
}
Enter fullscreen mode Exit fullscreen mode

新的layout.tsx:

import Hydration from "@/provider/hydration";
import Store from "@/provider/store";

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Store>
          <Hydration>{children}</Hydration>
        </Store>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

一般来说,这些是额外的步骤:

  1. 使用创建一个常量 queryClient new QueryClient(options)(设置staleTime很重要,否则,React Query 会在数据到达客户端后立即重新获取数据)。

  2. 用于await queryClient.prefetchQuery(...)要预取的每个查询。尽可能
    并行await Promise.all(...)获取查询。

  3. 可以有一些未预加载的查询。这些查询不会被服务器渲染;相反,它们会在应用程序交互后在客户端加载。这对于仅在用户交互后显示的内容或页面较靠后的内容非常有用,可以避免阻塞更重要的内容。

  4. <HydrationBoundary state={dehydrate(queryClient)}>使用来自框架加载器的 dehydratedState来包装你的树。

一个重要的细节

当使用 React Query 进行服务端渲染时,实际上有三个 queryClient 实例参与该过程:

  1. 预加载阶段:
    在渲染之前,会创建一个 queryClient 来预取数据。
    必要的数据会被获取并存储在这个 queryClient 中。

  2. 服务器渲染阶段:
    数据预取完成后,会被脱水(序列化)并发送到服务器渲染进程。
    服务器会创建一个新的 queryClient 并注入脱水后的数据。
    这确保服务器为客户端生成完整填充的 HTML。

  3. 客户端渲染阶段:
    脱水数据 (Dehydrated data) 被传递到客户端。
    客户端会创建另一个 queryClient 并重新加载数据。
    这确保客户端以相同的数据启动,保持一致性,并跳过初始数据获取。

这确保所有进程都以相同的数据开始,因此它们可以返回相同的标记。

然后,通过这个useQuery()钩子,你可以像平常一样使用预取查询,数据将在预加载阶段进行预取。这意味着,当你useQuery()在组件中使用预取查询时,React Query 会在组件渲染之前自动处理数据的预取。这确保了数据在需要时可用,从而有助于提高应用程序的性能。

"use client";
import getData from "@/api/getData";
import { useQuery } from "@tanstack/react-query";

 const { data: profiles } = useQuery({
   queryKey: ["profiles", "user"],
   queryFn: () => getData("profiles"),
 });
 const { data: permissions } = useQuery({
   queryKey: ["permissions", "user"],
   queryFn: () => getData("permissions"),
 });
Enter fullscreen mode Exit fullscreen mode

服务器内存消耗高

在 React Query 中,为每个请求创建一个 QueryClient 时,它会生成一个特定于该客户端的独立缓存。此缓存会在内存中保留一段指定的时间,称为gcTime。如果在此期间请求量很大,则可能导致服务器内存消耗严重。

默认情况下,服务器上的gcTime设置为Infinity,这意味着禁用手动垃圾回收,请求完成后会自动清除内存。但是,如果您设置的gcTime 不是 Infinity,则您有责任尽早清除缓存,以防止内存占用过高。

避免将gcTime设置为 0,因为这可能会导致 Hydration 错误。Hydration Boundary 会将渲染所需的数据放入缓存中。如果垃圾回收器在渲染完成之前移除这些数据,可能会导致问题。建议将其设置为 2 * 1000,以便应用有足够的时间引用这些数据。

要管理内存消耗并在不再需要缓存时清除缓存,您可以queryClient.clear()在处理请求并将脱水状态发送到客户端后调用。或者,您可以选择较小的gcTime来更快地自动清除内存。这可以确保高效的内存使用,并防止服务器出现与内存相关的问题。

例子:

const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        gcTime: 2 * 2000, // this sets the garbage collection time to 2 seconds
      },
    },
  });
Enter fullscreen mode Exit fullscreen mode

总而言之,React Query 简化了数据获取和缓存的过程,尤其是在处理服务器端渲染时。通过提前在服务器上获取数据并将其无缝传输到客户端,React Query 确保了流畅一致的用户体验。此外,借助调整内存管理设置等功能,开发者可以微调性能以满足其应用程序的需求。借助 React Query,开发者可以专注于构建引人入胜的应用程序,而无需担心复杂的数据管理任务,最终提供更快、响应更灵敏的用户体验。

鏂囩珷鏉ユ簮锛�https://dev.to/rayenmabrouk/advanced-server-rendering-react-query-with-nextjs-app-router-bi7
PREV
为什么 NestJS 是 Node 后端开发的新黄金标准
NEXT
如何使用 Git 和 GitHub 进行协作编程?