next-with-apollo,s-s-r 不起作用,请求在客户端完成

Posted

技术标签:

【中文标题】next-with-apollo,s-s-r 不起作用,请求在客户端完成【英文标题】:next-with-apollo, s-s-r does not work, request is done on client 【发布时间】:2021-07-05 09:41:57 【问题描述】:

我的代码很短,我尝试使用 https://github.com/lfades/next-with-apollo ,next-with-apolo。 但是s-s-r在我的情况下不起作用,我还在做客户电话,也许有人可以指导我。

我与阿波罗 ->

import withApollo from "next-with-apollo";
import ApolloClient,  InMemoryCache  from "apollo-boost";
import  ApolloProvider  from "@apollo/react-hooks";

export default withApollo(
  ( initialState ) => 
    return new ApolloClient(
      uri: "http://localhost:4000/graphql",
      credentials: "include",
      cache: new InMemoryCache().restore(initialState || ),
    );
  ,
  
    render: ( Page, props ) => 
      return (
        <ApolloProvider client=props.apollo>
          <Page ...props />
        </ApolloProvider>
      );
    ,
  
);

以及它自己的页面

import gql from "graphql-tag";
import  useQuery  from "@apollo/react-hooks";
import withApollo from "../../HOC/withApollo";
import  getDataFromTree  from "@apollo/react-s-s-r";

const QUERY = gql`
  
    me 
      user 
        email
      
    
  
`;

const Profile = () => 
  const  data  = useQuery(QUERY);

  console.log(data);

  return <div>data?.me.user?.email</div>;
;

export default withApollo(Profile,  getDataFromTree );

但请求仍在客户端中完成。

【问题讨论】:

您的组件中有useQuery,您的期望是什么?如果您希望请求发生在服务器上,我们需要将其移至getServerSideProps 【参考方案1】:

这是我正在使用的解决方案。它允许将页面(主要组件)包装在 HOC 中,然后请求 s-s-r 或不请求 s-s-r。

src
│   
│      
│
└───pages
│   │   _app.tsx
│   │   _document.tsx
|   |   index.tsx
│   │
│   |
└───apollo
    │   withApollo.ts
    │   createWithApollo.tsx

createWithApollo.tsx

import React from "react";
import App from "next/app";
import Head from "next/head";
import  ApolloProvider  from "@apollo/client";

// On the client, we store the Apollo Client in the following variable.
// This prevents the client from reinitializing between page transitions.
let globalApolloClient: any = null;

/**
 * Installs the Apollo Client on NextPageContext
 * or NextAppContext. Useful if you want to use apolloClient
 * inside getStaticProps, getStaticPaths or getServerSideProps
 * @param NextPageContext | NextAppContext ctx
 */
export const initOnContext = (ac: any, ctx: any) => 
  const inAppContext = Boolean(ctx.ctx);

  // We consider installing `withApollo( s-s-r: true )` on global App level
  // as antipattern since it disables project wide Automatic Static Optimization.
  if (process.env.NODE_ENV === "development") 
    if (inAppContext) 
      console.warn(
        "Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n" +
          "Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n"
      );
    
  

  // Initialize ApolloClient if not already done
  const apolloClient =
    ctx.apolloClient ||
    initApolloClient(ac, ctx.apolloState || , inAppContext ? ctx.ctx : ctx);

  // We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server.
  // Otherwise, the component would have to call initApollo() again but this
  // time without the context. Once that happens, the following code will make sure we send
  // the prop as `null` to the browser.
  apolloClient.toJSON = () => null;

  // Add apolloClient to NextPageContext & NextAppContext.
  // This allows us to consume the apolloClient inside our
  // custom `getInitialProps( apolloClient )`.
  ctx.apolloClient = apolloClient;
  if (inAppContext) 
    ctx.ctx.apolloClient = apolloClient;
  

  return ctx;
;

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  NormalizedCacheObject initialState
 * @param  NextPageContext ctx
 */
const initApolloClient = (apolloClient: any, initialState: any, ctx: any) => 
  // Make sure to create a new client for every server-side request so that data
  // isn't shared between connections (which would be bad)
  if (typeof window === "undefined") 
    return createApolloClient(apolloClient(ctx), initialState, ctx);
  

  // Reuse client on the client-side
  if (!globalApolloClient) 
    globalApolloClient = createApolloClient(
      apolloClient(ctx),
      initialState,
      ctx
    );
  

  return globalApolloClient;
;

/**
 * Creates a withApollo HOC
 * that provides the apolloContext
 * to a next.js Page or AppTree.
 * @param  Object withApolloOptions
 * @param  Boolean [withApolloOptions.s-s-r=false]
 * @returns (PageComponent: ReactNode) => ReactNode
 */
export const createWithApollo = (ac: any) => 
  return ( s-s-r = false  = ) => (PageComponent: any) => 
    const WithApollo = ( apolloClient, apolloState, ...pageProps : any) => 
      let client;
      if (apolloClient) 
        // Happens on: getDataFromTree & next.js s-s-r
        client = apolloClient;
       else 
        // Happens on: next.js csr
        client = initApolloClient(ac, apolloState, undefined);
      

      return (
        <ApolloProvider client=client>
          <PageComponent ...pageProps />
        </ApolloProvider>
      );
    ;

    // Set the correct displayName in development
    if (process.env.NODE_ENV !== "production") 
      const displayName =
        PageComponent.displayName || PageComponent.name || "Component";
      WithApollo.displayName = `withApollo($displayName)`;
    

    if (s-s-r || PageComponent.getInitialProps) 
      WithApollo.getInitialProps = async (ctx: any) => 
        const inAppContext = Boolean(ctx.ctx);
        const  apolloClient  = initOnContext(ac, ctx);

        // Run wrapped getInitialProps methods
        let pageProps = ;
        if (PageComponent.getInitialProps) 
          pageProps = await PageComponent.getInitialProps(ctx);
         else if (inAppContext) 
          pageProps = await App.getInitialProps(ctx);
        

        // Only on the server:
        if (typeof window === "undefined") 
          const  AppTree  = ctx;
          // When redirecting, the response is finished.
          // No point in continuing to render
          if (ctx.res && ctx.res.finished) 
            return pageProps;
          

          // Only if dataFromTree is enabled
          if (s-s-r && AppTree) 
            try 
              // Import `@apollo/react-s-s-r` dynamically.
              // We don't want to have this in our client bundle.
              const  getDataFromTree  = await import(
                "@apollo/client/react/s-s-r"
              );

              // Since AppComponents and PageComponents have different context types
              // we need to modify their props a little.
              let props;
              if (inAppContext) 
                props =  ...pageProps, apolloClient ;
               else 
                props =  pageProps:  ...pageProps, apolloClient  ;
              

              // Take the Next.js AppTree, determine which queries are needed to render,
              // and fetch them. This method can be pretty slow since it renders
              // your entire AppTree once for every query. Check out apollo fragments
              // if you want to reduce the number of rerenders.
              // https://www.apollographql.com/docs/react/data/fragments/
              await getDataFromTree(<AppTree ...props />);
             catch (error) 
              // Prevent Apollo Client GraphQL errors from crashing s-s-r.
              // Handle them in components via the data.error prop:
              // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
              console.error("Error while running `getDataFromTree`", error);
            

            // getDataFromTree does not call componentWillUnmount
            // head side effect therefore need to be cleared manually
            Head.rewind();
          
        

        return 
          ...pageProps,
          // Extract query data from the Apollo store
          apolloState: apolloClient.cache.extract(),
          // Provide the client for s-s-r. As soon as this payload
          // gets JSON.stringified it will remove itself.
          apolloClient: ctx.apolloClient,
        ;
      ;
    

    return WithApollo;
  ;
;

function createApolloClient(apolloClient: any, initialState: any, ctx: any) 
  // The `ctx` (NextPageContext) will only be present on the server.
  // use it to extract auth headers (ctx.req) or similar.
  apolloClient.s-s-rMode = Boolean(ctx);
  apolloClient.cache.restore(initialState);

  return apolloClient;

值得一提的是,这里我使用“apollo-upload-client”来上传文件,但你可以使用默认链接。

withApollo.ts

import  createWithApollo  from "./createWithApollo";
import  ApolloClient, InMemoryCache  from "@apollo/client";
import  NextPageContext  from "next";
import  createUploadLink  from "apollo-upload-client";

const createClient = (ctx: NextPageContext) =>
    new ApolloClient(

        cache: new InMemoryCache(),
        link: createUploadLink(
            uri: process.env.NEXT_PUBLIC_SERVER_URL as string,
            credentials: "include",
            headers: 
                cookie:
                    (typeof window === "undefined"
                        ? ctx?.req?.headers.cookie
                        : undefined) || "",
            ,
        )
    );

export const withApollo = createWithApollo(createClient);

_app.tsx

   import Router from "next/router";
    import  AppProps  from "next/app";

    
  
    
    function MyApp( Component, pageProps : AppProps) 
 
    
      return (
          <Component ...pageProps /> 
      );
    
    
    export default MyApp;

但是你需要用 Apollo 将页面包装到 HOC 中,你可以传递 s-s-r true 或者保留默认 false。如果你像往常一样执行 graphql all 。 您只需要在您呈现的页面中调用它,而不是在您呈现它的组件中。

pages/index.tsx

import React from "react";
import  withApollo  from "../apollo/withApollo";


const Index = () => <div></div>;

export default withApollo( s-s-r: true )(Index);

【讨论】:

【参考方案2】:

由于您在配置文件组件中写入了const data = useQuery(QUERY);,因此正在客户端调用请求。因此,当组件在客户端呈现时,就会调用该查询。

如果您只想在服务器端(即 s-s-r)调用该查询,则使用 getServerSideProps 方法并在其中调用给定查询并将结果作为道具传递给配置文件组件

例子:

const Profile = ( data ) => 
  // Render data...


// This gets called on every request
export async function getServerSideProps() 
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return  props:  data  


export default Profile; 

注意:您可以导入***范围内的模块以在 getServerSideProps 中使用。 getServerSideProps 中使用的导入不会被捆绑到客户端。 这意味着您可以直接在 getServerSideProps 中编写服务器端代码。这包括从文件系统或数据库中读取。

【讨论】:

这不是我要找的, @RomanHrybinchuk 您的代码在服务器端和客户端都运行。如果您希望该查询只在服务器端运行,那么您必须在 getServerSideProps 方法中编写该查询。 是的@SandroMennel,我可以附上解决方案

以上是关于next-with-apollo,s-s-r 不起作用,请求在客户端完成的主要内容,如果未能解决你的问题,请参考以下文章

npm run build:s-s-r 在 Angular 8 中不起作用

ReactDOM.hydrate() 在初始 s-s-r (React JS) 后不起作用

next.js 中的 apollo-client 与 `next-with-apollo` VS next.js 文档常见问题解答中显示的方法(不使用 `getDataFromTree`)

如何处理 Nuxt s-s-r 错误并显示自定义 404 || 500 页?

Meteor/s-s-r/Apollo 客户端 - 尝试使用 Apollo 设置 s-s-r 并没有找到 fetch

s-s-r 有啥好处:动态导入的真正道具?