LoginSignup
1
1

More than 1 year has passed since last update.

【Shopify/Relay/NextJs/TypeScript】shopifyのcustomerAccessTokenを初回アクセス時にrelay-nextjsのwithRelayで取れずnextのGetServerSidePropsを使った話

Last updated at Posted at 2022-07-28

はじめに

最近業務でshopify/Relay/NextJsでECサイトをフロントエンド開発をする機会があり、shopifyの「customerオブジェクトにアクセスするためのcustomerAccessToken」の初回取得ができずハマった箇所があったためその備忘録です。

前提として、GraphQLライブラリはRelayを使用しており、customerAccessTokenはライブラリのnookiesでhttp only管理し、サーバーからしか参照・取得できない設計にしています。
※この辺の実装は今回は割愛。

結論:relayで初回ページアクセス時にtokenが必要な場合はrelay-nextjsのserverSidePropsではなくnextのGetServerSidePropsを使う

初回リダイレクト時からcustomerAccessTokenトークン取得可
.
.

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実行したレスポンスを子コンポーネントに渡す。これにより最短でqueryを子コンポーネントに渡すことが出来、子側でuseFragmentも問題なく展開される。

customerのGraphQLクエリ定義&fetchCustomerQuery関数

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のserverSidePorps・variablesFromContextを使ってトークン参照・取得しようとしたところ、ページ初回アクセス(リダイレクト時)にcustomerAccessTokenがundefinedになり、ページリロードをするまでqueryデータの参照ができない状況になりました。
relay-nextjs開発者のコメントを読んだところ、そういった設計になっているようですね。

初回リダイレクト時customerAccessTokenはundefinedリロードするとトークン取得可
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();
  },
});

以上です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1