【问题标题】:Does useQuery run on server-side rendering?useQuery 是否在服务器端渲染上运行?
【发布时间】:2021-07-13 17:46:36
【问题描述】:

我是 Nextjs 新手,对 Nextjs 中的客户端渲染和服务器端渲染有一些疑问

  1. 我看到有两种方法可以在 Nextjs 上获取数据。其中之一是使用 useQuery 钩子,但只能在 React 组件函数上调用。是不是意味着它只在客户端渲染页面时才运行
  2. 我阅读了一篇关于如何将apolloClient 连接到 Nextjs 的帖子。它说

始终为 SSR 创建一个新的 apolloClient 实例,并且只为 CSR 创建一个 apolloClient 实例

这是示例代码

  export function initializeApollo(initialState = null) {
    const _apolloClient = apolloClient ?? createApolloClient();

    // If your page has Next.js data fetching methods that use Apollo Client,
    // the initial state gets hydrated here
    if (initialState) {
      // Get existing cache, loaded during client side data fetching
      const existingCache = _apolloClient.extract();

      // Restore the cache using the data passed from
      // getStaticProps/getServerSideProps combined with the existing cached data
      _apolloClient.cache.restore({ ...existingCache, ...initialState });
    }

    // For SSG and SSR always create a new Apollo Client
    if (typeof window === "undefined") return _apolloClient;

    // Create the Apollo Client once in the client
    if (!apolloClient) apolloClient = _apolloClient;
    return _apolloClient;
  }

谁能解释一下?如果问题很愚蠢,我很抱歉

【问题讨论】:

    标签: next.js apollo apollo-client


    【解决方案1】:

    在下一个 JS 中:

    • SSR - 服务器端渲染 - getServerSideProps
    • SSG - 生成静态站点 - getStaticPaths & getStaticProps
    • CSR - 客户端渲染 - 其他一切

    请务必注意,SSG 函数是在服务器端运行的。

    在客户端上,您只想创建一个 Apollo 客户端的全局实例。创建多个 Apollo 客户端实例将使与客户端保持同步变得具有挑战性。这个难点是因为 Apollo Cache、Apollo Link 等都将存储在不同的 Apollo Client 实例中。

    在 Next 中,通常将 Apollo Client 的全局实例放在页面_app.js 上,并使用Apollo Provider。在其他客户端页面上,您将使用调用单个全局实例的 useQuery 挂钩。

    服务器端 (SSR) 函数 getStaticPropsgetServerSideProps 无法访问 Apollo 的客户端实例、Next 的客户端实例或其他服务器端函数。因此,您必须在每个使用 getStaticPathsgetStaticPropsgetServerSideProps 并需要访问 Apollo 客户端的页面上定义 Apollo 连接,否则服务器端调用将无法使用它。

    由于第一个rule of hooks 是它们只能在顶层(客户端)调用,因此您不能在服务器端函数中使用它们。不,您不能在 Next SSR 或 SSG 函数中运行 useQuery

    您提供的示例是保持缓存同步并且是outdated in how it is defining the client。这是一个更符合官方文档的简化示例。


    graphqlClient.js

    import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
    
    // Used server and client side - can't use react hooks
    export const graphqlClient = new ApolloClient({
      cache: new InMemoryCache(),
      link: new HttpLink({
        uri: 'YOUR_GQL_ENDPOINT',
      }),
      ssrMode: typeof window === 'undefined',
    });
    

    _app.js - 所有客户端页面都使用的单个实例,因为它封装了整个应用程序

    import graphqlClient from 'my/path/graphqlClient';
    
    const App = ({ Component, pageProps }) => {
      const client = graphqlClient();
      return (
        <ApolloProvider client={client}>
          <Component {...pageProps} />
        </ApolloProvider>
      );
    };
    

    客户端的每个页面/组件都可以使用 useQuery 钩子,因为 Apollo 客户端将应用程序包装在 _app.js

    客户端查询

    import { gql, useQuery } from '@apollo/client';
    
    const About = () => {
     const { data } = useQuery(YOUR_QUERY); // uses your single instance defined in _app.js
     return (
       ...
     )
    }
    

    每个使用 SSR 或 SSG 功能并需要访问 Apollo 的页面都必须实例化一个新的 Apollo 实例。

    SSG

    import graphqlClient from 'my/path/graphqlClient';
    
    //does not have access to _app.js or client and must define new Apollo Client instance
    export const getStaticProps = async () => {
      const client = graphqlClient();//
      const { data } = await client.query({query: YOUR_QUERY});
    };
    
    export const getStaticPaths = async () => {
      const client = graphqlClient();
      const { data } = await client.query({query: YOUR_QUERY});
    };
    

    SSR

    import graphqlClient from 'my/path/graphqlClient';
    
    //does not have access to _app.js or client and must define new Apollo Client instance
    export const getServerSideProps = async () => {
      const client = graphqlClient();
      const { data } = await client.query({query: YOUR_QUERY});
    };
    

    最后,为了简化操作,您可以使用 graphql-code-generator 自动生成 Apollo 查询、变异等钩子(以及 TS 用户的类型)以及服务器端兼容的 query and mutation functions for Next.js

    【讨论】:

    • 感谢您的回答。它非常详细,并且有很大帮助。还有一个问题,每当我们创建 Apollo Client 的实例时,它也会创建 Apollo Cache、Apollo Link 等,对吧?并且服务器端不需要对它做任何事情(Apollo 缓存、Apollo Link)?
    • Apollo 将添加您在创建客户端时定义的任何内容。在上面的示例中,缓存和链接已定义并可用于服务器和客户端。但是,服务器端调用只能访问它们的一个客户端及其当前状态。因此,SSR 缓存基本上是空的,或者只包含在调用时在同一个 SSR 函数中进行的 Apollo 调用。有一些方法可以将服务器端调用配置为彼此更加同步,但这本身就是一个完整的帖子,并不总是需要 - 特别是在 SSG 站点中。
    • 关于问题 1.,根据这篇博文:apollographql.com/blog/apollo-client/next-js/…,似乎 useQuery 确实会在服务器渲染期间触发 HTTP 查询。我仍然不清楚会发生什么,因为如果您不设置类似 getDataFromTree 或任何采用 Apollo 客户端缓存并将其传递回客户端的系统,这些查询将“丢失”。
    • 默认情况下 - Apollo useQuery 是当前 (React 17) 组件安装在浏览器中时触发的 React 钩子。 Apollo 客户端查询是在服务器上使用的。默认情况下——每个 Next 服务器端函数都独立于应用程序向其提供数据——它们不知道其他服务器端函数做什么或当前应用程序状态。这是 nextjs 和 apollo 的自定义实现,允许您使用 apollo useQuery ssr w/next - github.com/shshaw/next-apollo-ssr