これからShopify Hydrogenで開発する人のために。私達の会社の開発ガイドラインを公開します。
こんにちは。代表兼CTOの高崎です。
私達の会社ではShopify純正ヘッドレスコマースフレームワークHydrogenを使ったフロントエンド開発も行っています。
まだ新しいフレームワークなので情報が少なく、私達も手探りで開発を進めることも多いのですが、少しでもHydrogenでつまづく人が少なくなればと思い、わたしたちの実案件を通じた学びを反映したHydrogen開発ガイドラインを公開します。
これからHydrogenで開発をする人はぜひ参考にしてくださいね。
目次
Shopify Hydrogen V1 開発ガイドライン
Shopifyテーマに対するHydrogenの優位性
Shopifyのフロントエンドを作る際にShopifyテーマ(WordPressのテーマと概念は似ている。モノリス構成)とHydrogen(ヘッドレス構成)を利用する方法がある。 シンプルに言えば「早く行きたければLiquidでゆけ、遠くへ行きたければHydrogenでゆけ」。 Hydrogenでサイトを作る方が難易度が高いが、そのかわりに以下メリットがある。
- 動的な更新が多いサイトでも高速化できる
- フロントエンドは一つのままにEC部分はShopify、CMSはWordPressといった複数のバックエンドとつなぎこむことができる
- 大規模なサイトでチーム開発がしやすい
- URL構造を自由に決めることができるためSEOがしやすい
- 多言語、他通貨対応がやりやすいため越境ECがやりやすい
なお執筆時点でShopifyが買収したReactフレームワーク「Remix」をベースにしたV2が出ているが、ここではV1について解説する。
V2はRemixの中にHydorgen V1コンポーネントが吸収されたような形になっており、V1を学ぶことはV2で実装する上でも無駄にはならない。
💡 Hydrogenとは
shopifyの構築に用いられるフロントエンドのフレームワーク。React/Typescriptで記述されており、ECサイトに必要な様々なコンポーネントやツールが用意されている。特徴的なのはRSC(React Server Component)の採用で、これによりコンポーネント単位でのサーバーサイドからの更新が可能になる。
🔴 新規テーマ作成
Hydrogenには「Hello World」と「Demo Store」の2つのスターターが用意されている。それぞれJavaScript
とTypeScript
を選択できる。型がある方が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 lint
とyarn 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などを代わりにインストールする。voltaやnなどを使用してもよいかと思います。
.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のstoreDomain
とstorefrontToken
とstorefrontApiVersion
を書き換える。
また環境変数は本番環境とローカルの環境変数を条件分岐させて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.ts
とindex.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を連携させることができる。
🟢 デプロイ
上記を参照し、各種デプロイ設定を行う。 特に理由がなければ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.ts
のprivateStorefrontToken
で読み込めば良い。
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