加入我们,从10月8日至10日,在纽约市学习有关 GraphQL 联邦和 API 平台工程的新技巧、趋势和新闻。加入我们,参加2024年纽约市的 GraphQL 大会
文档
免费开始

服务器端渲染


服务器端渲染SSR)是现代Web应用的性能优化。它允许您在服务器上渲染应用程序的初始状态为原始HTML和CSS之后再将其发送到浏览器。这意味着用户不必等到他们的浏览器下载和初始化React(或Angular,Vue等)内容才可用:

ServerBrowserServerBrowserRenders initial state of example.com on serverDisplays raw HTML and CSSInitializes view layer and "rehydrates" it with returned dataRequests example.comReturns raw HTML and CSS

提供了一种方便的API来支持服务器端渲染,包括一个执行所有用于渲染组件树的查询的函数。您不需要对查询进行任何修改即可支持此API。

与客户端渲染的区别

当您在服务器端渲染您的React应用程序时,大部分代码与其客户端对应版本相同,但也存在一些重要的例外:

初始化Apollo客户端

以下是一个服务器端初始化Apollo客户端的示例:

import {
ApolloClient,
createHttpLink,
InMemoryCache
} from '@apollo/client';
const client = new ApolloClient({
ssrMode: true,
link: createHttpLink({
uri: 'https://127.0.0.1:3010',
credentials: 'same-origin',
headers: {
cookie: req.header('Cookie'),
},
}),
cache: new InMemoryCache(),
});

您将会注意到与典型客户端初始化的区别有几点

  • 您需要提供 ssrMode: true。这可以防止 Apollo Client 无谓地重新查询查询,同时它还使您能够使用下面的 getDataFromTree 函数。

  • 而不是提供一个 uri 选项,您需要将 HttpLink 实例提供给 link 选项。这使得您可以在服务器端向 GraphQL 端点发送请求时指定任何所需的认证详情。

    请注意,您还可能需要确保您的 GraphQL 端点被配置为接受来自 SSR 服务器的 GraphQL 操作(例如,通过白名单它的域名或 IP)。

您的 GraphQL 端点由执行 SSR 的同一服务器托管是可能的,也是有效的。在这种情况下,Apollo Client 不需要通过网络请求来执行查询。有关详细信息,请参阅下面的 避免为本地查询使用网络

示例

让我们看看在 Node.js 应用中 SSR 的一个示例。此示例使用 Express 和 React Router v4,尽管它可以与任何服务器中间件以及支持 SSR 的任何路由器一起工作。

首先,这是一个示例 app.js 文件,不包括将 React 渲染为 HTML 和 CSS 的代码:

直到现在,每当这个示例服务器收到请求时,它首先初始化 Apollo Client,然后创建一个 React 树,该树被包裹在 ApolloProviderStaticRouter 组件中。该树的 内容取决于请求的路径和 StaticRouter 定义的路线。

⚠️ 注意事项

为每个请求创建一个新的Apollo Client实例非常重要。否则,您对请求的响应可能包括来自先前请求的敏感缓存结果。

使用 getDataFromTree 执行查询

由于我们的应用程序使用 Apollo Client,React 树中的某些组件可能使用 useQuery 钩子执行 GraphQL 查询。我们可以通过 getDataFromTree 函数指导 Apollo Client 执行树组件所需的全部查询。

此函数遍历整个树并执行它遇到的每个必需的 查询(包括嵌套查询)。它返回一个 Promise。当Apollo Client缓存中所有结果数据都准备好时,Promise得到解决。

Promise 解决时,您就准备好渲染React树并返回它,以及当前的Apollo Client缓存状态。

注意,如果您将React树直接渲染为字符串(而不是下面的基于组件的示例),则需要使用 renderToStringWithData 而不是 getDataFromTree。这将确保客户端React水解通过使用 ReactDOMServer.renderToString 来生成字符串。

以下代码替换了示例中 app.use 调用中的 TODO 注释:

app.js
// Add this import to the top of the file
import { getDataFromTree } from "@apollo/client/react/ssr";
// Replace the TODO with this
getDataFromTree(App).then((content) => {
// Extract the entirety of the Apollo Client cache's current state
const initialState = client.extract();
// Add both the page content and the cache state to a top-level component
const html = <Html content={content} state={initialState} />;
// Render the component to static markup and return it
res.status(200);
res.send(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`);
res.end();
});

渲染为静态标记的顶级 Html 组件的定义可能如下所示:

components/html.js
export function Html({ content, state }) {
return (
<html>
<body>
<div id="root" dangerouslySetInnerHTML={{ __html: content }} />
<script dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g, '\\u003c')};`,
}} />
</body>
</html>
);
}

这将导致渲染的React树作为 root div 的子项,并将初始缓存状态分配给 __APOLLO_STATE__ 全局对象。

此示例中的 replace 调用是通过转义字符串字面量中的 < 字符来执行的,以防止通过在字符串字面量中存在 </script> 进行的跨站脚本攻击。

重新激活客户端缓存

尽管服务器端缓存的状?态可供 __APOLLO_STATE__ 使用,但它还不在 客户端 缓存中。 InMemoryCache提供了一个有用的 restore 函数,可以用来响应从其他缓存实例获取的数据恢复其状态。

您可以在Apollo Client客户端初始化中这样重新激活缓存:

const client = new ApolloClient({
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
uri: 'https://example.com/graphql'
});

现在当客户端应用程序运行其初始查询时,数据被立即返回,因为它们已经在缓存中啦!

初始化期间覆盖获取策略

如果你的初始查询使用了 网络优先缓存和网络 获取策略,你可以向 Apollo Client 提供一个 ssrForceFetchDelay 选项,以在初始化期间跳过强制获取这些查询。这样,即使这些查询最初也只使用缓存:

const client = new ApolloClient({
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
link,
ssrForceFetchDelay: 100, // in milliseconds
});

避免本地查询使用网络

如果你的 GraphQL 端点是由你渲染的同一服务器托管,你可以在执行 SSR 查询时选择性地避免使用网络。如果服务器环境中(例如在 Heroku 上)localhost 被防火墙阻止,这将非常有用。

一个选项是使用 通过使用本地 来获取数据,而不是进行网络请求。为了实现这一点,在服务器上创建 Apollo Client 时,你应使用 SchemaLink 而不是使用 createHttpLinkSchemaLink 使用您的模式和上下文立即运行查询,而不进行任何额外的网络请求:

import { ApolloClient, InMemoryCache } from '@apollo/client'
import { SchemaLink } from '@apollo/client/link/schema';
// ...
const client = new ApolloClient({
ssrMode: true,
// Instead of "createHttpLink" use SchemaLink here
link: new SchemaLink({ schema }),
cache: new InMemoryCache(),
});

跳过一个查询

如果您想要在 SSR 期间故意跳过某个特定的 查询,可以在该查询的选项中包含 ssr: false。通常这意味着该组件在服务器上以“正在加载”状态渲染。例如:

function withClientOnlyUser() {
useQuery(GET_USER_WITH_ID, { ssr: false });
return <span>My query won't be run on the server</span>;
}
上一页
乐观突变结果
下一页
使用 Babel 编译查询
为文章评分评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,又称Apollo GraphQL。

隐私政策

公司