サクッとECサイトを構築できるReactベースのHydrogen入門
2022/4/23 修正 デプロイ2-3 workers.js → worker.js
先日、私の所属コミュニティOver40WebClub主催で タイトルのような勉強会を開催、そこで登壇して報告しました、 勉強中の人が勉強したことを報告する勉強会ドリブン勉強法という形です。 connpasのページ
そのときにつくったデモサイトや資料などを GitHubのリポジトリでREADMEにまとめました。 GitHub:サクッとECサイトを構築できるReactベースのHydrogen入門
この記事ではその時に報告した内容のうち、 デモでHydrogenアプリを構築したときの内容を記録したいと思います。 まだまだ理解が浅いので、僕の理解で書いているところで間違っているところがあると思いますので、気づいた方、ご指摘をお願いします!
勉強会の開催にあたって、Over40webClubの ピータン@pitang1965さんの大きなサポートを頂きました! yoko@yokoiwasaki6さんに司会をしていただきました! Over40WebClubのみなさまにたくさんのコメント、はげましをいただきました! ありがとうございました!
Table of content
- Hydrogenとはなにか
- Hydrogenのよいところ
- アプリ構築の手順紹介
Hydrogenとはなにか
Shopifyによる、Shopifyのための、Shopifyのフロントエンドフレームワークです。ShopifyではStorefrontAPIを提供していて、これを利用してヘッドレスコマースサイトが注目されています。GatsbyやNext.jsでは、Shopifyと接続するためのスターターもあります。
海外ではヘッドレスコマースがけっこうあるようです。また、おそらく100円均一のダイソーさんのECサイトもShopifyを使ったヘッドレスコマースだと思われます。
Gridsome Victoria Beckham Beauty Next.js Koala Easter Sale | Up To 20% Off Sitewide Sale On Now | Koala AU Nuxt.js Reusable Water Bottles | Reusable Coffee Cups | Chilly's ダイソー
HydrogenはShopifyが自らてがけた、フロントエンドフレームワークで、現在はまだ開発途中です。ヘッドレスCMSにおける課題を解決できそうな期待の持てるものだと思いました。 今回アプリの公開にはCLOUDFLAREのWorkersを使用していますが、もうすぐ専用のホスティングサービスとして、Oxygenがリリースされそうです。現在はShopifyの一部のマーチャントで実験的に使用しているということでした。
Hydrogenのよいところ
Reactの最新機能を先取りしています。ブログで次の3つのポイントが紹介されていました。
Streamning SSR
SSRとCSRのいいとこどりをしています。 サーバーサイドでのデータフェッチとクライアントサイドでのレンダリングを同時にやってしまおうという機能です。最初のレンダリングにおけるTBTの削減につながっているようです。
React Server Components
Reactで課題だった、すべてのコンポーネントが出来るまでレンダーされないという課題を解決しています。サーバーサイドとクライアントサイドのコードを明示的にかき分けることで、バンドルサイズの縮減につながっているようです。
CDN,キャッシュ戦略
静的なページについては、CDNでキャッシュしておいて、高速に表示をしようというものです。基本的にはSSRなので、SSGに劣る部分をこれで補っているのかなと思いました。 キャッシュ戦略では、秒単位でキャッシュするものから年単位でキャッシュするものを明確にかき分けることができるようでした。
アプリ構築の手順紹介
プロジェクト開始
- プロジェクト用フォルダ作成→プロジェクト開始
yarn create hydrogen-app
このコマンドでアプリのコード一式構築。途中プロジェクト名を聞かれるので入力します。 2. この段階では、デモンストレーション用のshopifyストアのデータに接続しています。まずは、表示をみてみましょう。
// プロジェクトのフォルダを移動
cd <your-project-name>
// 依存関係をインストール
yarn
// 開発サーバーを起動
yarn dev
Shopifyでのストア構築、設定
Shopifyパートナープログラムに登録 登録すると、開発ストアをいくつも構築することができます。ずっと無料です。
左メニューからストア管理 → ストアを追加するボタン
ストアタイプを選ぶ → 開発ストア
その後いろいろ設定をして保存をすると、ストアが立ち上がります。 shopifyによるホスティングで、すでにストアをインターネット上に公開はされていますが、開発ストアはすべてパスワードで保護されている状態です。 要するに実際の店舗としては使えないということです。実際の案件ではここでストアを構築した後、所有権を移行することで、マーチャントがストアの管理をできるようになります。 4. 商品、コレクションを登録 いくつか商品とコレクションを登録しておくとあとでHydrogenに接続したときに、わかりやすいです。 商品を登録する コレクションについて
ShopifyからAPIを取得できるようにする
ShopifyからAPIを取得できるようにするために、アプリを設定します。
ストア用のアプリを開発する
カスタムアプリ開発を許可
カスタムアプリを作成
ストアフロントAPIスコープを設定する→アプリをインストールする
- ストアフロントで提供しているデータのうち、取得を許可するものを選びます。
- 今回はすべての項目にチェックを入れます。
- 保存 → アプリをインストール
アクセストークンを取得
下の画面が表示されていないときは「API資格情報」をクリック
Hydrogen側API設定
Shopify側で設定したデータをもとに、Hydrogenアプリ側でAPIと接続するための設定をします。
必要なデータ
URL ストアURLの一部
アクセストークン Shopify側でアプリを作成したときに取得したもの
gitignore
まずは、設定情報を書き込むshopifu.configをGit管理でオープンにしてしまわないように、.gitignoreに設定します。
ここまでの操作でGit管理はしていませんが、Git管理は基本になるので、設定しておきます。
.gitignore
。。。 # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* # Vite output dist 一番うしろに追加します。 # storefront API config shopify.config.js
shopify.config
- storeDomain:の値 → URLの一部
- storefrontToken → shopifyアプリ作成のときに取得したもの
// こちらはHydrogenサンプルストアのデータ、 export default { storeDomain: 'hydrogen-preview.myshopify.com', storefrontToken: '3b580e70970c4528da70c98e097c2fa0', storefrontApiVersion: '2022-04', };
これで、自分のお店のデータがAPIを通じて、Hydrogenに渡ってくる設定ができました。開発サーバーで確認してみます。
yarn dev
ちょっとカスタマイズ
Reactベースなので、React出来る方はどんどんカスタマイズできます。Hydrogen独自のコンポーネントがたくさんあって、それを使うとカートボタンや商品リストや詳細ページが簡単に作れます。 スタイリングにはTailwindCSSが使われているので、デザインも比較的かんたんにカスタマイズできました。
- コンタクトページ追加
src/routes/[handle].server.jsxというところが固定ページのテンプレートのようです。
Shopify側でページをWysiwygエディタで作成すると、
/pages/○○
というルーティングで表示されます。コンタクトページも/pages/contact
で同様に表示されるのですが、フォーム自体は渡ってきませんでした。 そこで、routes直下にcontact.server.jsx
を作成して、そこにフォームを作りました。 フォームデザインは以下のサイトからいただきました。 Tail-kit Components and templates for Tailwind CSS 2.0 また、フォーム機能は、GetFormというサービスを使っています。
import Layout from '../components/Layout.server';
export default function Index({country = {isoCode: 'US'}}) {
return (
<Layout>
<form
action="https://getform.io/f/76aa1111-aaba-491c-be0c-fc47a12142e2"
className="flex w-full space-x-3"
method="post"
>
<div className="w-full max-w-2xl px-5 py-10 m-auto mt-10 bg-white rounded-lg shadow dark:bg-gray-800">
<div className="mb-6 text-3xl font-light text-center text-gray-800 dark:text-white">
Contact us !
</div>
<div className="grid max-w-xl grid-cols-2 gap-4 m-auto">
<div className="col-span-2 lg:col-span-1">
<div className=" relative ">
<input
type="text"
id="contact-form-name"
className=" rounded-lg border-transparent flex-1 appearance-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
placeholder="Name"
/>
</div>
</div>
<div className="col-span-2 lg:col-span-1">
<div className=" relative ">
<input
type="text"
id="contact-form-email"
className=" rounded-lg border-transparent flex-1 appearance-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
placeholder="email"
/>
</div>
</div>
<div className="col-span-2">
<label className="text-gray-700" htmlFor="name">
<textarea
className="flex-1 appearance-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
id="comment"
placeholder="Enter your comment"
name="comment"
rows={5}
cols={40}
defaultValue={' '}
/>
</label>
</div>
<div className="col-span-2 text-right">
<button
type="submit"
className="py-2 px-4 bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500 focus:ring-offset-indigo-200 text-white w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg "
>
Send
</button>
</div>
</div>
</div>
</form>
</Layout>
);
}
デプロイ
公式ドキュメントを参考に進めました。
- CLOUDFRAREにアクセスしてWorkersのサービスを作成します。
左メニュー → Workers → サービスを作成
サービス名を設定して「サービスの作成」ボタン
設定ファイル、workerファイル作成
ルートに2つのファイルを新規作成
wrangler.toml (デプロイの設定ファイル)
name = "PROJECT_NAME" <- ここは、自分の設定したサービス名 type = "javascript" account_id = "" workers_dev = true route = "" zone_id = "" compatibility_date = "2022-01-28" compatibility_flags = ["streams_enable_constructors"] [site] bucket = "dist/client" entry-point = "dist/worker" [build] upload.format = "service-worker" command = "yarn && yarn build"
worker.js
// If the request path matches any of your assets, then use the `getAssetFromKV` // function from `@cloudflare/kv-asset-handler` to serve it. Otherwise, call the // `handleRequest` function, which is imported from your `App.server.jsx` file, // to return a Hydrogen response. import {getAssetFromKV} from '@cloudflare/kv-asset-handler'; import handleRequest from './src/App.server'; import indexTemplate from './dist/client/index.html?raw'; function isAsset(url) { // Update this RE to fit your assets return /\.(png|jpe?g|gif|css|js|svg|ico|map)$/i.test(url.pathname); } async function handleAsset(url, event) { const response = await getAssetFromKV(event, {}); // Custom cache-control for assets if (response.status < 400) { const filename = url.pathname.split('/').pop(); const maxAge = filename.split('.').length > 2 ? 31536000 // hashed asset, will never be updated : 86400; // favicon and other public assets response.headers.append('cache-control', `public, max-age=${maxAge}`); } return response; } async function handleEvent(event) { try { const url = new URL(event.request.url); if (isAsset(url)) { return await handleAsset(url, event); } return await handleRequest(event.request, { indexTemplate, cache: caches.default, context: event, // Buyer IP varies by hosting provider and runtime. You should provide this // as an argument to the `handleRequest` function for your runtime. // Defaults to `x-forwarded-for` header value. buyerIpHeader: 'cf-connecting-ip', }); } catch (error) { return new Response(error.message || error.toString(), {status: 500}); } } addEventListener('fetch', (event) => event.respondWith(handleEvent(event)));
CloudFlareのKV asset handlerをインストール
npm install @cloudflare/kv-asset-handler
package.json 変更
// 以下の行を削除 "build:worker": "cross-env WORKER=true vite build --outDir dist/worker --ssr @shopify/hydrogen/platforms/worker", // 以下の行を追加 "build:worker": "cross-env WORKER=true vite build --outDir dist/worker --ssr worker",
アカウントIDを設定して、デプロイ
CF_ACCOUNT_ID=<YOUR_CLOUDFLARE_ACCT_ID> wrangler publish
アカウントIDの設定は一度しておくと、その後は、wrangler publish だけでデプロイできます
おわりに
いかがでしたでしょうか? フレームワークというのはつくづくありがたいものだと思いました。 しかし、こういうものが実務レベルでサワれるようになるにはまだまだです。 出来る日がくるのだろうかと心配になるときもありますが、 とにかくコツコツとぼちぼちと続けていこうと思います。
そんほんす
ありがとうございます! 最近の100円ショップはけっこうネットにもショップがあるのです?1つの商品を大量に注文できて、便利なのです。 ヘッドレスコマースっぽいのを見て、今の勉強頑張ろ!って思った次第です?
ハル
勉強会お疲れ様でした! ダイソーがShopifyで、ECサイトを運営しているなんて驚きました(実店舗なイメージが非常に強かったです)。 勉強になりました。