これからShopify Hydrogenで開発する人のために。私達の会社の開発ガイドラインを公開します。

こんにちは。代表兼CTOの高崎です。
私達の会社ではShopify純正ヘッドレスコマースフレームワークHydrogenを使ったフロントエンド開発も行っています。
まだ新しいフレームワークなので情報が少なく、私達も手探りで開発を進めることも多いのですが、少しでもHydrogenでつまづく人が少なくなればと思い、わたしたちの実案件を通じた学びを反映したHydrogen開発ガイドラインを公開します。
これからHydrogenで開発をする人はぜひ参考にしてくださいね。

Shopify Hydrogen V1 開発ガイドライン

Shopifyテーマに対するHydrogenの優位性

Shopifyのフロントエンドを作る際にShopifyテーマ(WordPressのテーマと概念は似ている。モノリス構成)とHydrogen(ヘッドレス構成)を利用する方法がある。 シンプルに言えば「早く行きたければLiquidでゆけ、遠くへ行きたければHydrogenでゆけ」。 Hydrogenでサイトを作る方が難易度が高いが、そのかわりに以下メリットがある。

  1. 動的な更新が多いサイトでも高速化できる
  2. フロントエンドは一つのままにEC部分はShopify、CMSはWordPressといった複数のバックエンドとつなぎこむことができる
  3. 大規模なサイトでチーム開発がしやすい
  4. URL構造を自由に決めることができるためSEOがしやすい
  5. 多言語、他通貨対応がやりやすいため越境ECがやりやすい

なお執筆時点でShopifyが買収したReactフレームワーク「Remix」をベースにしたV2が出ているが、ここではV1について解説する。
V2はRemixの中にHydorgen V1コンポーネントが吸収されたような形になっており、V1を学ぶことはV2で実装する上でも無駄にはならない。

💡 Hydrogenとは

shopifyの構築に用いられるフロントエンドのフレームワーク。React/Typescriptで記述されており、ECサイトに必要な様々なコンポーネントやツールが用意されている。特徴的なのはRSC(React Server Component)の採用で、これによりコンポーネント単位でのサーバーサイドからの更新が可能になる。

Hydrogen overview

🔴 新規テーマ作成

Hydrogen quickstart

Hydrogenには「Hello World」と「Demo Store」の2つのスターターが用意されている。それぞれJavaScriptTypeScriptを選択できる。型がある方がshopifyのメタフィールドのデータなどは扱いやすい。

  • Hello World → 最小限のテンプレート
  • Demo Store → 一通りの機能を既に実装しているテンプレート
yarn create @shopify/hydrogen

コマンドの指示に従って作成する。

*以降、Demo Store / TSを使用した場合を想定してガイドラインを作成


🟠 環境設定

yarn / npm / pnpm

yarn.lockとpackage-lock.jsonが混在しない方がよいので、チーム内でどれかに統一する。

package.jsonにフィールドに使用するパッケージマネジャーを記述しておく。また、lockファイルはgitignoreせずにリポジトリに追加する。

"packageManager": "[email protected]",

VSCode

初期設定では下記のVSCodeのエクステンションのインストールが推奨されている。

{
  "recommendations": [
    "graphql.vscode-graphql",
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss"
  ]
}

Demo Storeテンプレートの場合は、初期設定でprettierが含まれている。チーム内でコードの整形フォーマットを変更する場合は、適宜prettierのconfigファイルを作成して設定する。

ESLint

yarn lintyarn lint-tsでlintチェックを行う。ESLintの設定は適宜追加する。

// console.*を許可する場合
rules: {
    'no-console': 'off',
}

Vite

ビルドツールとしてviteが採用されている。

vite.config.jsにrollup optionなどを設定すると、本番環境で動作しないことがあるので、基本的にデフォルトのまま使用する。

/// <reference types="vitest" />
import {defineConfig} from 'vite';
import hydrogen from '@shopify/hydrogen/plugin';

export default defineConfig({
  plugins: [hydrogen()],
  resolve: {
    alias: [{find: /^~\/(.*)/, replacement: '/src/$1'}],
  },
  optimizeDeps: {
        // このあたりは適宜調整する
    include: ['@headlessui/react', 'clsx', 'react-use', 'typographic-base'],
  },
  test: {
    globals: true,
    testTimeout: 10000,
    hookTimeout: 10000,
    maxThreads: 1,
    minThreads: 1,
  },
});

Node

nodenvを使用して.node-versionでバージョンを管理する。Windowsで開発する場合は、nvmなどを代わりにインストールする。voltanなどを使用してもよいかと思います。

.node-versionに記載された設定は、大抵のデプロイ環境におけるnodeのバージョン指定にもそのまま適用される。

package.jsonのenginesにもnodeのバージョンを指定しておく(yarn install時のnode指定)。

17.9.1
"engines": {
  "node": ">=17"
},

🟣 shopify連携

shopify管理画面から<YOUR_SHOPIFY_DOMAIN.myshopify.com/admin/settings/apps/development>へアクセスし、カスタムアプリを追加する。

storefront APIのアクセストークンを発行し、ドメイン名とアクセストークンを.envファイルに控える。

hydrogen.config.tsのstoreDomainstorefrontTokenstorefrontApiVersionを書き換える。

また環境変数は本番環境とローカルの環境変数を条件分岐させてhydrogen.config.tsで読み込む事が可能。

shopifyとの連携ができたら下記URLでGraphiQL explorerを開くことができる。データが取得できるか確認する。

http://localhost:3000/___graphql

query {
  shop {
    name
    description
  }
}

環境変数

環境変数は.envに記述しておく。viteでは接頭辞にハッシュをつけたものだけがクライアントで取得できるため(参照)、VITE_を付ける。

VITE_SHOPIFY_STORENAME=**********.myshopify.com
VITE_SHOPIFY_STOREFRONT_TOKEN=**********

Cloudflare Workersの場合、globalに宣言された型が存在する場合、Cloudflareに登録した環境変数を優先的に読み込みに行く。

そのため@types/env.d.tsなどを作成しておき、下記のように型宣言しておく。

export {};

declare global {
  const SHOPIFY_STORENAME: string;
  const SHOPIFY_STOREFRONT_TOKEN: string;
}

Cloudflareに登録した環境変数が存在する場合はそちらを読みに行き、存在しない場合はローカルの値を読みに行く条件分岐をconst.tsに記述する。

// SHOPIFY_STORENAMEでCloudflareに登録
// VITE_SHOPIFY_STORENAMEでローカルの.envに登録

export const STORE_DOMAIN =
  typeof SHOPIFY_STORENAME === 'string'
    ? SHOPIFY_STORENAME
    : import.meta.env.VITE_SHOPIFY_STORENAME;

この値をhydrogen.config.tsで使用する。

import {STORE_DOMAIN} from '~/lib/const';

export default defineConfig({
  shopify: {
    storeDomain: STORE_DOMAIN,

🟡 開発設定

Tailwind.css

デフォルトのcssフレームワークはTailwind.cssが採用されているので、そのままTWを使用するのが無難。

src/styles/index.cssにまとめてCSS Tokenを記述し、それをtailwind.config.jsで読み込む。postcss.config.jsなどは基本初期設定のままでよい。

バンドルサイズを肥大化させないために@applyディレクティブの使用は最小限にする方が良い。

/* CSS Token */
:root {
  --font-size-display: 3rem;
  --font-size-heading: 2rem;
    --font-size-lead: 1.125rem;
theme: {
    extend: {
            fontSize: {
        display: ['var(--font-size-display)', '1.1'],
        heading: ['var(--font-size-heading)', '1.25'],
        lead: ['var(--font-size-lead)', '1.333'],

アセット

SVGは画像ではなくインラインコードとして挿入する。画像として挿入するのは、レンダリングが遅いこともあり一般的にアンチパターンであることに加え、デプロイした環境で表示されないことがある。

export function AccountIcon(props: IconProps) {
  return (
    <Icon {...props}>
      <title>Accounts</title>
      <circle cx="20" cy="10.5" r="4.5" strokeWidth="2" />
      <path
        d="M20 19C13.4375 19 9.5 20.2857 9.5 28H30.5C30.5 20.2857 26.5625 19 20 19Z"
        strokeWidth="2"
      />
    </Icon>
  );
}

jpg, pngなどはassetsフォルダに格納する。ただし、ファイルサイズが大きい場合はshopifyの管理画面にファイルを追加しておき、urlを直接コピペする方がWebP対応やCDNキャッシュをしてくれるため便利。

favicon.icoなど、ルートに展開すべきファイルはpublicフォルダに追加する。

GraphQL

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

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

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

ディレクトリの留意点

Hydrogenでは各階層ごとにindex.tsindex.server.tsを用意し、コンポーネントをまとめてexportしている。これにより他のファイルからimportする場合の記述をまとめることが可能。

client componentとshared componentはindex.tsに記述し、server componentはindex.server.tsに記述する。

例)component > global配下

export {CartBadge} from './CartBadge';
export {CartDrawer} from './CartDrawer.client';
export {Drawer} from './Drawer.client';
export {Footer} from './Footer.server';
export {Layout} from './Layout.server';
export {NotFound} from './NotFound.server';

さらにglobalディレクトリをcomponentディレクトリでまとめてexportする。

export * from './global/index';
export * from './global/index.server';

こうすることにより、様々な階層に存在するcomponentをまとめてimportすることができる。

import {Text, Button, CartBadge, CartDrawer, Section} from '~/components';
import {Header, Footer, NotFound} from '~/components/index.server';

アナリティクス

Google Tag ManagerやShopify Analyticsを連携させることができる。

  1. GTMを追加する場合はこのドキュメントに従って設定する
  2. shopifyの管理画面にアナリティクスのデータを送信する場合はこのドキュメントに従って設定する

🟢 デプロイ

Deploy a Hydrogen storefront

上記を参照し、各種デプロイ設定を行う。 特に理由がなければShopify純正ホスティングOxygenを使用する。 Oxygenを使用しない場合は以下に留意する。 またHydrogenが出始めた当時、RSCコンポーネントが日本語をうまく処理できず文字化けすることがありfly.io上でDockerコンテナを構築し、ホスティングすることがあったが、現在その問題は解消されているように見える。

Private Storefront Tokenの設定

hydrogenではOxygenを使用しない場合にはRate Limit対策は必須です。

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

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

{ "delegate_access_scope": [ "unauthenticated_read_content_entries", "unauthenticated_read_content_models", "unauthenticated_write_checkouts", "unauthenticated_read_checkouts", "unauthenticated_read_product_listings", "unauthenticated_read_product_pickup_locations", "unauthenticated_read_product_tags", "unauthenticated_read_selling_plans", "unauthenticated_write_customers", "unauthenticated_read_customers", "unauthenticated_read_customer_tags", "unauthenticated_read_product_inventory", "unauthenticated_read_content" ] }
{
    "access_token": "shpat_**********"
}

発行されたトークンはenvに秘匿し、hydrogen.config.tsprivateStorefrontTokenで読み込めば良い。

Netlify

RSC(react server component)が動作するためには、Netlify Edge Functionsを使用する必要があるが、これはまだベータ版の機能のため、Netlifyのパスワード機能を使用すると動作しなくなります。(ベータ版の制限

Cloudflare

Cloudflareの場合、環境変数をこのドキュメントに従い追加し、値はencryptし秘匿させておく。

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

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

デプロイ用のコマンドをpackage.jsonに設定しておく。github actions等のCIを設定しない場合、手動でdeployコマンドを叩く。

"scripts": {
    "build": "shopify hydrogen build --entry worker",
    "deploy": "wrangler publish"

🟨 わたしたちの会社のHydrogen制作実績

低温調理器BONIQ(ボニーク)
*Hydrogen V1
yori.so gallery & label
*Hydrogen V2

記事カテゴリー