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 页?