在首次访问时,无法通过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();
},
});
以上就是。