我有很短的代码,我尝试使用 https://github.com/lfades/next-with-apollo ,next-with-apolo。 但是SSR在我的情况下不起作用,我还在做客户电话,也许有人可以指导我。
我和阿波罗 ->
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-ssr";
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 });
但是请求仍在客户端中完成。
答案 0 :(得分:0)
请求在客户端被调用,因为您在配置文件组件中写入了 const { data } = useQuery(QUERY);
。因此,当组件在客户端呈现时,就会调用该查询。
如果您只想在服务器端(即 ssr)调用该查询,则使用 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 中编写服务器端代码。这包括从文件系统或数据库中读取数据。
答案 1 :(得分:0)
这是我正在使用的解决方案。它允许将页面(主要组件)包装在 HOC 中,然后请求 ssr 或不 ssr。
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({ ssr: 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.ssr=false]
* @returns {(PageComponent: ReactNode) => ReactNode}
*/
export const createWithApollo = (ac: any) => {
return ({ ssr = false } = {}) => (PageComponent: any) => {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }: any) => {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
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 (ssr || 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 (ssr && AppTree) {
try {
// Import `@apollo/react-ssr` dynamically.
// We don't want to have this in our client bundle.
const { getDataFromTree } = await import(
"@apollo/client/react/ssr"
);
// 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 SSR.
// 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 ssr. 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.ssrMode = 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中,可以通过ssr true或者保留默认false。如果你像往常一样执行 graphql 所有 . 你只需要在你渲染的页面内调用它,而不是在你渲染它的组件中。
pages/index.tsx
import React from "react";
import { withApollo } from "../apollo/withApollo";
const Index = () => <div></div>;
export default withApollo({ ssr: true })(Index);