Closed14

Shopify Hydrogen

KazuhoKazuho

Hydrogenの思想では、queryはコンポーネントごとに取得できることを目指している。トップレベルですべてのデータを取得して、子コンポーネントにバケツリレーするのではなく、それぞれのcomponent内に直接記述する。

fetchの並列処理を行う箇所はnetwork request waterfallsを避けるためにpreload: trueを設定する。

const {data} = useShopQuery({
  query: QUERY,
  variables: {
    handle: '***',
  },
	preload: true,
});
KazuhoKazuho

Cloudflare Workersへデプロイする場合。
Cloudflareの場合、環境変数をこのドキュメントに従い追加し、encryptして値は秘匿させておく。もしくはwrangler secret put <KEY>で変数の登録をする。

後から確認しやすいようにwrangler.tomlに下記のように、必要な変数をコメントで残しておく。

# The necessary environment variables are:
# - SHOPIFY_STORENAME
# - SHOPIFY_STOREFRONT_TOKEN
KazuhoKazuho

現在、Internal Server Errorのコンポーネント自体は変更することができない。
そのため自前でError Boundary処理を行い、カスタムErrorページを表示させる。

Error Boundary処理は関数コンポーネントではなくClassコンポーネントを書く必要があるが、そのあたりを処理してくれる便利なパッケージがあるのでそれを使用する。

yarn add react-error-boundary

RSCでは直接は動かせないので、一旦clientコンポーネントを作成し、wrapする必要がある。
エラーのログもconsoleに吐くようにしておく。

import {ErrorBoundary, FallbackProps} from 'react-error-boundary';

export function CustomErrorBoundary({children}: {children: React.ReactNode}) {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback} onError={onError}>
      {children}
    </ErrorBoundary>
  );
}
const onError = (error: Error, info: {componentStack: string}) => {
  console.log('error.message', error.message);
  console.log('info.componentStack:', info.componentStack);
};

function ErrorFallback({resetErrorBoundary}: FallbackProps) {
  return (
    <div role="alert">
      <h1>
        表示できません
      </h1>
      <button type="button" onClick={resetErrorBoundary}>
        再試行する
      </button>
    </div>
  );
}

あとはApp.server.tsxで全体をwrapしてやればよい。

function App({request, response}: HydrogenRouteProps) {
  return (
    <CustomErrorBoundary>
......
KazuhoKazuho

Cloudflareにデプロイすると文字がたまにバグる。もしかするとマルチバイトの言語に対応してないのかもしれない。


Hydrogenはコンテンツを<meta data-flight>というchunksに分割しているらしい。その過程で、文字がマルチバイトのため、途中で分割してしまったのではないかと思う。
Hydrogenがdecodeするときに、streamオプションを渡してやると解決するのかもしれない。

// @shopify/hydrogen/dist/esnext/entry-server.js
function tagOnWrite(response) {
......
    response.write = (arg) => {
        if (arg instanceof Uint8Array) {
            savedChunks.push(decoder.decode(arg, stream)); // <--- adding stream option
KazuhoKazuho

NetlifyやVercelにもデプロイしてみたが、結果は変わらず。

KazuhoKazuho

デモストアに接続しても同様の現象が起きる。
結局、Dockerでローカルと同一環境を作り、fly.ioにデプロイすることで成功しました。

KazuhoKazuho

MarkDownのparserにはreact-remarkはnullが返ってきてしまった。
markedを代わりに使用した。

import {marked} from 'marked';

export function MarkDownRender({
  md,
  className = '',
}: {
  md: string;
  className?: string;
}): JSX.Element | null {
  const body = marked(md);
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: body,
      }}
      className={className}
    />
  );
}
KazuhoKazuho

hydrogenではOxygenを使用しない場合にはRate Limit対策は必須と書かれている。

通常のStorefront APIではPublic Tokenを使用するが、delegate access token(private token)を使用してStorefront APIを叩くらしい。

delegate access tokenここを参考に、GraphQL AdminかREST Admin APIを使用してmutationさせればトークンが発行される。
セキュリティ面から、delegateAccessScopeは必要最低限のもののみを指定する方がよいそうです。

このトークンはenvに秘匿し、hydrogen.config.tsprivateStorefrontTokenで読み込めば良い。

KazuhoKazuho

Hydrogenが用意しているSeoコンポーネントでは、type="product"の場合は画像を動的に設定できるがほかのページでは用意されていない。
カスタムで<head>内にタグを追加したい場合は、ここに説明があるように<Head>タグを使用する。
DefaultSeoコンポーネントに<Head>でデフォルトのOGP画像を設定しておく。type="product"のページでは自動で上書きされる模様。

import {Head} from '@shopify/hydrogen';

<Head>
  <meta property="og:image" content={DEFAULT_OGP_IMAGE} />
</Head>
このスクラップは2023/03/04にクローズされました