在首次访问时,无法通过relay-nextjs的withRelay获取Shopify的customerAccessToken,所以使用了next的GetServerSideProps

首先

最近我在业务中有机会使用Shopify/Relay/NextJs进行电子商务网站的前端开发,遇到了一个问题,即无法在Shopify中获取”customerAccessToken”以访问”customer对象”,以下是我记录的备忘录。

以GraphQL库使用Relay为前提,customerAccessToken在库的nookies中以http only方式管理,设计为只能从服务器中引用和获取。
※本次实现的细节暂不讨论。

结论:如果在初次访问页面时需要令牌,应该使用 next 的 GetServerSideProps 而不是 relay-nextjs 的 serverSideProps。

.
.

import type { NextPage, GetServerSideProps } from 'next';
import React, { Suspense, useEffect, useState } from 'react';
import { useQueryLoader } from 'react-relay';
import { createServerEnvironment } from '../lib/serverEnvironment';

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { parseCookies } = await import('nookies'); 
  const { token } = parseCookies(context); // サーバーから取得したcontextからtokenをparse
  if (!token) {
    return {
      redirect: {
        permanent: false,
        destination: '/',
      },
    };
  }
  return {
    props: {
      customerAccessToken: token ?? '', // parse済みのtokenをpropsでDemoPage?に渡す
    },
  };
};

type Props = {
  customerAccessToken: string;
};
// getServerSidePropsからPropsのcustomerAccessTokenでtokenを受け取る
const DemoPage: NextPage<Props> = ({ customerAccessToken }: Props) => {
  const [, loadCustomerQuery] =
    useQueryLoader<CustomerQueryType>(customerQuery);

  const [customer, setCustomerOrder] =
    useState<CustomerQueryResponse>();

  // useLazyLoadQueryではロードできないのでuseQueryLoader後にfetchQuery関数でquery実行
  useEffect(() => {
     // queryロードをしないとpropsで子コンポーネントに渡った先のuseFragmentでこける
    loadCustomerQuery({ customerAccessToken });
    // queryのfetch関数はgetServerProps内で叩くと、データは読まれるが「The server could not finish this Suspense boundary, likely due to an error during server rendering.Switched to client rendering」が吐かれるのでここで実行
    fetchCustomerQuery({
      environment: createServerEnvironment(),
      customerAccessToken,
    }).then((result) => setCustomer(result));
  // stateにデータをセットして子コンポーネントにpropsで渡す
  }, [customerAccessToken, loadCustomerQuery]);

  if (!isLoggedIn || !customer) {
    return <Loading />;
  }
  return (
    <Suspense fallback={<Loading />}>
      <ChildComponent customer={customer} />
    </Suspense>
  );
};
export default DemoPage;


只需通过fetchCustomerQuery来执行query的子组件在进行useFragment处理的时间上无法达到,因此可以通过useEffect加载query并直接将fetchCustomerQuery函数执行的响应传递给子组件。这样可以最快地将query传递给子组件,并且子组件中的useFragment也能够正常展开。


import { Environment, fetchQuery, graphql } from 'react-relay';
import type {
  customerQuery as CustomerQueryType,
  customerQuery$data as CustomerQueryResponse,
} from './__generated__/customerQuery.graphql';

export const customerQuery = graphql`
  query customerQuery($customerAccessToken: String!) {
    customer(customerAccessToken: $customerAccessToken) {
      firstName
      lastName
      defaultAddress {
        address1
        address2
        lastName
        firstName
        province
        zip
        city
        id
      }
      orders(first: 250) {
        edges {
          node {
            ...orderFragment
          }
        }
      }
    }
  }
`;

type FetchGetCustomerQuery = {
  environment: Environment;
  customerAccessToken: string;
};
export const fetchCustomerQuery = ({
  environment,
  customerAccessToken,
}: FetchCustomerQuery): Promise<
  CustomerResponse | undefined
> =>
  fetchQuery<CustomerQueryType>(
    environment,
    CustomerQuery,
    {
      customerAccessToken,
    },
  ).toPromise();

另一种情况:如果在relay-nextjs的serverSideProps中获取令牌。

由于使用了GraphQL库Relay,我尝试使用relay-nextjs的withRelay的serverSideProps和variablesFromContext来引用和获取令牌。但在页面首次访问(重定向时),customerAccessToken变成了undefined,在重新加载页面之前无法引用查询数据。根据relay-nextjs开发者的评论,似乎这是设计上的限制。

import { withRelay, RelayProps } from 'relay-nextjs';
import type {
  NextPageContext,
  NextPage,
  Redirect,
} from 'next';
import { GraphQLTaggedNode, useLazyLoadQuery, Variables } from 'react-relay';
import { customerQuery } from '../../lib/graphql/customerQuery';
import { customerQuery as CustomerQueryType } from '../../lib/graphql/__generated__/customerQuery.graphql';

type Props = {
  redirect?: Redirect;
  customerAccessToken: string;
};

type CompositeProps = RelayProps<Props, GetCustomerQueryType>;

const DemoPage: NextPage<CompositeProps, Props> = ({
  customerAccessToken,
}: CompositeProps) => {
const [
    customerInitialPreloadedQuery,
    setCustomerInitialPreloadedQuery,
  ] = useState<AnyPreloadedQuery | null>();

  const relayProps = getRelayProps(
    customerQuery,
    customerInitialPreloadedQuery as AnyPreloadedQuery | null,
  );
  const relayPropsCustomerAccessToken = relayProps.preloadedQuery?.variables
    .customerAccessToken as string;

  const accessToken =
    relayPropsCustomerAccessToken !== undefined
      ? customerAccessToken
      : customerAccessTokenState;


  const customer = useLazyLoadQuery<CustomerQueryType>(
    customerQuery,
    {customerAccessToken: accessToken},
  );
  useEffect(() => {
    const initialPreloadedQuery = getInitialPreloadedQuery({
      createClientEnvironment: () => getClientEnvironment()!,
    });
    setCustomerInitialPreloadedQuery(initialPreloadedQuery);
  }, []);

  if (!isLoggedIn) {
    return <Loading />;
  }
  return (
    <Suspense fallback={<Loading />}>
      <ChildComponent customer={customer} />
    </Suspense>
  );
};
export default withRelay(DemoPage, getCustomerQuery, {
  fallback: <Loading />,

  variablesFromContext: (ctx: NextRouter | NextPageContext) => {
    const { token } = parseCookies(ctx as NextPageContext);
    return {
      customerAccessToken: token ?? '',
    };
  },
  serverSideProps: async (ctx: NextPageContext | NextRouter) =>
    new Promise<Props>((resolve) => {
      const { token } = parseCookies(ctx as NextPageContext);
      if (token === undefined) {
        resolve({
          redirect: {
            permanent: false,
            destination: '/',
          },
          customerAccessToken: token ?? '',
        });
      } else {
        resolve({
          customerAccessToken: token ?? '',
        });
      }
    }),
  createClientEnvironment: () => getClientEnvironment()!,
  createServerEnvironment: async () => {
    const { createServerEnvironment } = await import(
      '../lib/server/serverEnvironment'
    );
    return createServerEnvironment();
  },
});

以上就是。

广告
将在 10 秒后关闭
bannerAds