微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

无法设置 Apollo 客户端来处理 NextJS

如何解决无法设置 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 headerscreateApolloClient headers 控制台日志都返回完整(和正确)的标头,包括我想要的 cookie。 enhancedFetch headers 只有 { accept: '*/*','content-type': 'application/json'}。所以这是第一个奇怪的地方。然后我收到有关缺少 JWT 的服务器错误,然后所有三个控制台日志都运行两次:未定义 initializeApollo 标头,createApolloClient 标头为空,而 EnhancedFetch 标头保持不变。

我的直觉告诉我,这与服务器在第一次运行时没有收到标头有关,所以它弄乱了以下控制台日志(我想这可能是客户端?)。在这一点上,我有点不知所措。我错过了什么??

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。