如何解决无法设置 Apollo 客户端来处理 NextJS
我已经研究这个好几天了,只是想不通!
我的 apolloClient 设置与 this article 中的设置几乎相同。我不想为我在整个应用程序中发出的每个请求都添加存储在浏览器 cookie 中的令牌,而是想在 ApolloClient 的初始化过程中设置它。
然而,我看到的是从我的服务器发送的 graphQL 错误,告诉我我没有提供 JWT,当我查看请求的标头时,Authorization
是空白的。最终,我需要它能够同时用于服务器端和客户端查询和变更。
pages/_app.tsx
// third party
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/client';
import { MuiThemeProvider,StylesProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from 'styled-components';
// custom
import Page from 'components/Page';
import theme from 'styles/theme';
import GlobalStyle from 'styles/globals';
import { useApollo } from 'lib/apolloClient';
function App({ Component,pageProps,router }: AppProps): JSX.Element {
const client = useApollo(pageProps);
return (
<>
<Head>
<title>My App</title>
</Head>
<ApolloProvider client={client}>
<MuiThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<StylesProvider>
<CssBaseline />
<GlobalStyle />
<Page>
<Component {...pageProps} />
</Page>
</StylesProvider>
</ThemeProvider>
</MuiThemeProvider>
</ApolloProvider>
</>
);
}
export default App;
pages/index.tsx
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useEffect,useState } from 'react';
import Head from 'next/head';
import { GetServerSidePropsContext,NextPageContext } from 'next';
import { Box,Grid,Typography } from '@material-ui/core';
import Widget from 'components/common/Widget';
import Loading from 'components/common/Loading';
import { addApolloState,initializeApollo } from 'lib/apolloClient';
import { MeDocument } from 'generated/types';
const Home: React.FC = () => {
return (
<>
<Head>
<title>Dashboard</title>
</Head>
<Grid container spacing={3}>
... other rendered components
</Grid>
</>
);
};
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
const client = initializeApollo({ headers: context?.req?.headers });
try {
await client.query({ query: MeDocument });
return addApolloState(client,{
props: {},});
} catch (err) {
// return redirectToLogin(true);
console.error(err);
return { props: {} };
}
};
export default Home;
lib/apolloClient.ts
import { useMemo } from 'react';
import {
ApolloClient,ApolloLink,InMemoryCache,normalizedCacheObject,} from '@apollo/client';
import { onError } from '@apollo/link-error';
import { createUploadLink } from 'apollo-upload-client';
import merge from 'deepmerge';
import { IncomingHttpHeaders } from 'http';
import fetch from 'isomorphic-unfetch';
import isEqual from 'lodash/isEqual';
import type { AppProps } from 'next/app';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
let apolloClient: ApolloClient<normalizedCacheObject> | undefined;
const createApolloClient = (headers: IncomingHttpHeaders | null = null) => {
console.log('createApolloClient headers: ',headers);
// isomorphic fetch for passing the cookies along with each GraphQL request
const enhancedFetch = async (url: RequestInfo,init: Requestinit) => {
console.log('enhancedFetch headers: ',init.headers);
const response = await fetch(url,{
...init,headers: {
...init.headers,'Access-Control-Allow-Origin': '*',// here we pass the cookie along for each request
Cookie: headers?.cookie ?? '',},});
return response;
};
return new ApolloClient({
// SSR only for Node.js
ssrMode: typeof window === 'undefined',link: ApolloLink.from([
onError(({ graphQLErrors,networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message,locations,path }) =>
console.log(
`[GraphQL error]: Message: ${message},Location: ${locations},Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}),// this uses apollo-link-http under the hood,so all the options here come from that package
createUploadLink({
uri: publicRuntimeConfig.graphqlEndpoint,// Make sure that CORS and cookies work
fetchOptions: {
mode: 'cors',credentials: 'include',fetch: enhancedFetch,}),]),cache: new InMemoryCache(),});
};
type InitialState = normalizedCacheObject | undefined;
type ReturnType = ApolloClient<normalizedCacheObject>;
interface IInitializeApollo {
headers?: IncomingHttpHeaders | null;
initialState?: InitialState | null;
}
export const initializeApollo = (
{ headers,initialState }: IInitializeApollo = {
headers: null,initialState: null,}
): ReturnType => {
console.log('initializeApollo headers: ',headers);
const _apolloClient = apolloClient ?? createApolloClient(headers);
// If your page has Next.js data fetching methods that use Apollo Client,the initial state
// get hydrated here
if (initialState) {
// Get existing cache,loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = merge(initialState,existingCache,{
// combine arrays using object equality (like in sets)
arrayMerge: (destinationArray,sourceArray) => [
...sourceArray,...destinationArray.filter(d => sourceArray.every(s => !isEqual(d,s))),],});
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
// 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;
};
export const addApolloState = (
client: ApolloClient<normalizedCacheObject>,pageProps: AppProps['pageProps']
): ReturnType => {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
}
return pageProps;
};
export function useApollo(pageProps: AppProps['pageProps']): ReturnType {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(
() => initializeApollo({ initialState: state }),[state]
);
return store;
}
我在 apolloClient.ts
文件中有三个控制台日志。当我重新加载页面时,initializeApollo headers
和 createApolloClient headers
控制台日志都返回完整(和正确)的标头,包括我想要的 cookie。 enhancedFetch headers
只有 { accept: '*/*','content-type': 'application/json'}
。所以这是第一个奇怪的地方。然后我收到有关缺少 JWT 的服务器错误,然后所有三个控制台日志都运行两次:未定义 initializeApollo 标头,createApolloClient 标头为空,而 EnhancedFetch 标头保持不变。
我的直觉告诉我,这与服务器在第一次运行时没有收到标头有关,所以它弄乱了以下控制台日志(我想这可能是客户端?)。在这一点上,我有点不知所措。我错过了什么??
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。