Closed6

Hydrogenを理解する 〜その3〜

popy1017popy1017

本チュートリアルで学習する内容

  • Collection Route を作成し、Hydrogen のファイルベースのルーティングシステムに慣れる。
  • handleでコレクションを検索する。
  • CollectionページのSEOタグを生成する。
  • CollectionページでShopify Analyticsを実装する。
  • Collectionに属する製品を取得する。

前提条件

以下が完了していること。
https://shopify.dev/custom-storefronts/hydrogen/getting-started/tutorial/fetch-data

popy1017popy1017

Step 1: Collection Routeを作成する

  • Hydrogenのsrc/routesディレクトリに追加されたコンポーネントは、すべてrouteとして登録される。
  • [handle]のような括弧の付いたファイル名は、:handleというルートパラメータに変換される。

Collectionページの構築を始めるには、 src/routes/collections/[handle].server.tsxというファイルを作成し、新しいCollectionルートを登録する。
そして、Layoutコンポーネント内のページにダイナミックハンドルを表示する。

src/routes/collections/[handle].server.tsx
import { useRouteParams } from "@shopify/hydrogen";

import { Layout } from "../../components/Layout.server";

export default function Collection() {
  const { handle } = useRouteParams();
  return (
    <Layout>
      <section className="p-6 md:p-8 lg:p-12">
        This will be the collection page for <strong>{handle}</strong>
      </section>
    </Layout>
  );
}

初期画面に表示されている Featured collectionをクリックすると、クリックしたものに応じて以下のような画面が表示される。

補足

src/routes/collections/[handle].server.tsxにはhttp://localhost:3000/collections/{handle}でアクセスでき、{handle}の部分がsrc/routes/collections/[handle].server.tsxの中で handleという変数として扱える。
src/routes/hogehoge/index.server.tsxにはhttp://localhost:3000/hogehogeでアクセスできる。

http://localhost:3000/collections/hoge にアクセスすると、This will be the collection page for hogeと表示される。

popy1017popy1017

Step 2: handle で Collection を検索する

  • Collectionのhandleを使用して、Collection を検索することができる
  • handleは、Collectionなどのリソースを識別するための一意の文字列
  • Collectionの作成時にhandleが指定されていない場合、handleはCollectionの元のタイトルから生成され、スペースはハイフンに置き換えられる。
    • 例: Freestyle collectionというタイトルで作成されたCollectionのhandleはfreestyle-collectionになる

CollectionはShopifyの概念。商品を特定の条件や任意でグルーピングして、コレクションというまとまりで管理できるようになるShopify標準の機能。

src/routes/collections/[handle].server.tsxに handle で Collectionを取得するGraphQLクエリを追加する。

src/routes/collections/[handle].server.tsx
import { gql, useShopQuery, useRouteParams } from "@shopify/hydrogen";

import { Layout } from "../../components/Layout.server";

export default function Collection() {
  const { handle } = useRouteParams();

  const {
    data: { collection },
  } = useShopQuery({
    query: QUERY,
    variables: {
      handle,
    },
  });

  return (
    <Layout>
      <section className="p-6 md:p-8 lg:p-12">
        This will be the collection page for <strong>{collection.title}</strong>
      </section>
    </Layout>
  );
}

// Add a Graphql query that retrieves a collection by its handle.
const QUERY = gql`
  query CollectionDetails($handle: String!) {
    collection(handle: $handle) {
      title
    }
  }
`;

修正前のコードでは handle の内容をそのまま出力していたが、修正後は GraphQL で取得したタイトルを表示するようになっている。

- This will be the collection page for <strong>{handle}</strong>
+ This will be the collection page for <strong>{collection.title}</strong>

DemoStoreには hoge というCollectionはないため、http://localhost:3000/collections/hogeはエラーになる。

popy1017popy1017

Step 3: SEOタグを生成しShopify Analyticsを実装する

ShopifyAnalyticsコンポーネントをHydrogenストアフロントに追加することで、Shopify管理画面のAnalyticsダッシュボードから主要な売上、注文、オンラインストア訪問者データを見ることができる。

import {
  gql,
  useShopQuery,
  Seo,
  useServerAnalytics,
  useRouteParams,
  ShopifyAnalyticsConstants,
} from "@shopify/hydrogen";
import { Suspense } from "react";

import { Layout } from "../../components/Layout.server";

export default function Collection() {
  const { handle } = useRouteParams();

  const {
    data: { collection },
  } = useShopQuery({
    query: QUERY,
    variables: {
      handle,
    },
  });

  useServerAnalytics({
    shopify: {
      pageType: ShopifyAnalyticsConstants.pageType.collection,
      resourceId: collection.id,
    },
  });

  return (
    <Layout>
      <Suspense>
        <Seo type="collection" data={collection} />
      </Suspense>
      <header className="grid w-full gap-8 p-4 py-8 md:p-8 lg:p-12 justify-items-start">
        <h1 className="text-4xl whitespace-pre-wrap font-bold inline-block">
          {collection.title}
        </h1>

        {collection.description && (
          <div className="flex items-baseline justify-between w-full">
            <div>
              <p className="max-w-md whitespace-pre-wrap inherit text-copy inline-block">
                {collection.description}
              </p>
            </div>
          </div>
        )}
      </header>
    </Layout>
  );
}

// The `Seo` component uses the collection's `seo` values, if specified. If not
// specified, then the component falls back to using the collection's `title` and `description`.
const QUERY = gql`
  query CollectionDetails($handle: String!) {
    collection(handle: $handle) {
      id
      title
      description
      seo {
        description
        title
      }
    }
  }
`;
popy1017popy1017

Step4: ProductとVariantを検索する

  • Productとは、販売者が販売する商品、デジタルダウンロード、サービス、ギフトカードなどを指す。
  • Productにサイズや色などのオプションがある場合、マーチャントはオプションの組み合わせごとにVariantを追加することができる。
    • 例: スノーボードに青と緑の2色があるとすると、青いスノーボードと緑のスノーボードはVariantとなる

サンプルコードで使用されているMoneyコンポーネントは、Storefront API の MoneyV2 オブジェクトの文字列をHydrogen 設定ファイルの defaultLocale に従ってレンダリングする。

  1. Collection内の各製品のタイトル、価格、画像を表示する ProductCard コンポーネントを作成する。
/src/components/ProductCard.server.tsx
import { Link, Image, Money } from "@shopify/hydrogen";

export default function ProductCard({ product }) {
  const { priceV2: price, compareAtPriceV2: compareAtPrice } =
    product.variants?.nodes[0] || {};

  const isDiscounted = compareAtPrice?.amount > price?.amount;

  return (
    <Link to={`/products/${product.handle}`}>
      <div className="grid gap-6">
        <div className="shadow-sm rounded relative">
          {isDiscounted && (
            <label className="subpixel-antialiased absolute top-0 right-0 m-4 text-right text-notice text-red-600 text-xs">
              Sale
            </label>
          )}
          <Image
            className="aspect-[4/5]"
            data={product.variants.nodes[0].image}
            alt="Alt Tag"
          />
        </div>
        <div className="grid gap-1">
          <h3 className="max-w-prose text-copy w-full overflow-hidden whitespace-nowrap text-ellipsis ">
            {product.title}
          </h3>
          <div className="flex gap-4">
            <span className="max-w-prose whitespace-pre-wrap inherit text-copy flex gap-4">
              <Money withoutTrailingZeros data={price} />
              {isDiscounted && (
                <Money
                  className="line-through opacity-50"
                  withoutTrailingZeros
                  data={compareAtPrice}
                />
              )}
            </span>
          </div>
        </div>
      </div>
    </Link>
  );
}
  1. GraphQL クエリを更新して、Collection に属するProductとVariantの取得を実装する
import {
  gql,
  useShopQuery,
  useRouteParams,
  useServerAnalytics,
  ShopifyAnalyticsConstants,
  Seo,
} from "@shopify/hydrogen";

import { Layout } from "../../components/Layout.server";
import ProductCard from "../../components/ProductCard.server";
import { Suspense } from "react";

export default function Collection() {
  const { handle } = useRouteParams();

  const {
    data: { collection },
  } = useShopQuery({
    query: QUERY,
    variables: {
      handle,
    },
  });

  useServerAnalytics({
    shopify: {
      pageType: ShopifyAnalyticsConstants.pageType.collection,
      resourceId: collection.id,
    },
  });

  return (
    <Layout>
      <Suspense>
        <Seo type="collection" data={collection} />
      </Suspense>
      <header className="grid w-full gap-8 p-4 py-8 md:p-8 lg:p-12 justify-items-start">
        <h1 className="text-4xl whitespace-pre-wrap font-bold inline-block">
          {collection.title}
        </h1>

        {collection.description && (
          <div className="flex items-baseline justify-between w-full">
            <div>
              <p className="max-w-md whitespace-pre-wrap inherit text-copy inline-block">
                {collection.description}
              </p>
            </div>
          </div>
        )}
      </header>

      <section className="w-full gap-4 md:gap-8 grid p-6 md:p-8 lg:p-12">
        <div className="grid-flow-row grid gap-2 gap-y-6 md:gap-4 lg:gap-6 grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
          {collection.products.nodes.map((product) => (
            <ProductCard key={product.id} product={product} />
          ))}
        </div>
      </section>
    </Layout>
  );
}

const QUERY = gql`
  query CollectionDetails($handle: String!) {
    collection(handle: $handle) {
      id
      title
      description
      seo {
        description
        title
      }
      image {
        id
        url
        width
        height
        altText
      }
      products(first: 8) {
        nodes {
          id
          title
          publishedAt
          handle
          variants(first: 1) {
            nodes {
              id
              image {
                url
                altText
                width
                height
              }
              priceV2 {
                amount
                currencyCode
              }
              compareAtPriceV2 {
                amount
                currencyCode
              }
            }
          }
        }
      }
    }
  }
`;
このスクラップは2022/11/12にクローズされました