Next.jsで高速eコマースアプリを構築する | エピソード2: ヘッドレスコマースAPIとの統合

こんにちは！前回のエピソードでは、Next.jsプロジェクトの環境をセットアップし、TypeScript、Tailwind CSS、ESLint、Prettierを統合しました。シリーズ2回目となる今回は、ヘッドレスコマースプラットフォーム（Shopify Storefront API）をNext.jsに統合し、商品一覧ページ（PLP: Product Listing Page）を構築します。APIクライアントの設定やデータの取得方法についても詳しく解説します！

## このエピソードのゴール

- Shopify Storefront APIをNext.jsに接続する。
- 商品データを取得して表示するAPIクライアントを作成。
- `getStaticProps`を使って商品一覧ページを構築。
- Tailwind CSSでレスポンシブな商品リストUIをデザイン。

## 必要なもの

- 前回のプロジェクト（`next-ecommerce`）がセットアップ済み。
- Shopifyストア（またはテスト用の無料ストア）。
- Shopify Storefront APIのアクセストークン。
- 基本的なTypeScriptとReactの知識。

---

## ステップ1: Shopify Storefront APIの準備

Shopify Storefront APIは、ヘッドレスコマースで商品データやカート情報を取得するためのGraphQL APIです。以下の手順で準備します：

1. **Shopifyストアの設定**：
   - Shopifyアカウントを作成（無料トライアル可）。
   - テスト用の商品をいくつか追加（例: Tシャツ、ジャケットなど）。

2. **Storefront APIの有効化**：
   - Shopify管理画面で「アプリとセールスチャネル」→「アプリを開発」を選択。
   - 新しいアプリを作成し、「Storefront API」を有効化。
   - アクセストークンを取得（後で使用）。

3. **GraphQLエンドポイント**：
   - エンドポイントは通常`https://あなたのストア.myshopify.com/api/2024-10/graphql.json`の形式。

---

## ステップ2: APIクライアントの作成

Shopify Storefront APIと通信するために、GraphQLクライアントをセットアップします。`graphql-request`ライブラリを使用します。

### ライブラリのインストール

ターミナルで以下のコマンドを実行：

```bash
npm install graphql-request
```

### APIクライアントの実装

`src/lib/shopify.ts`ファイルを作成し、以下のコードを追加：

```ts
import { GraphQLClient } from 'graphql-request';

const endpoint = 'https://あなたのストア.myshopify.com/api/2024-10/graphql.json';
const storefrontAccessToken = 'あなたのアクセストークン';

export const shopifyClient = new GraphQLClient(endpoint, {
  headers: {
    'X-Shopify-Storefront-Access-Token': storefrontAccessToken,
  },
});
```

**注意**: アクセストークンは機密情報です。本番環境では`.env`ファイルを使って管理します。`.env.local`に以下を追加：

```env
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=あなたのアクセストークン
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ENDPOINT=https://あなたのストア.myshopify.com/api/2024-10/graphql.json
```

その後、`src/lib/shopify.ts`を以下のように修正：

```ts
import { GraphQLClient } from 'graphql-request';

const endpoint = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ENDPOINT!;
const storefrontAccessToken = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN!;

export const shopifyClient = new GraphQLClient(endpoint, {
  headers: {
    'X-Shopify-Storefront-Access-Token': storefrontAccessToken,
  },
});
```

---

## ステップ3: 商品データを取得するクエリ

商品一覧を取得するためのGraphQLクエリを作成します。`src/lib/queries.ts`ファイルを作成し、以下のクエリを追加：

```ts
export const GET_PRODUCTS_QUERY = `
  query GetProducts($first: Int!) {
    products(first: $first) {
      edges {
        node {
          id
          title
          handle
          priceRange {
            minVariantPrice {
              amount
              currencyCode
            }
          }
          images(first: 1) {
            edges {
              node {
                url
                altText
              }
            }
          }
        }
      }
    }
  }
`;
```

このクエリは、商品のID、タイトル、URLハンドル、価格、画像を取得します。

次に、商品データを取得する関数を`src/lib/shopify.ts`に追加：

```ts
import { GraphQLClient } from 'graphql-request';
import { GET_PRODUCTS_QUERY } from './queries';

const endpoint = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ENDPOINT!;
const storefrontAccessToken = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN!;

export const shopifyClient = new GraphQLClient(endpoint, {
  headers: {
    'X-Shopify-Storefront-Access-Token': storefrontAccessToken,
  },
});

interface Product {
  id: string;
  title: string;
  handle: string;
  priceRange: {
    minVariantPrice: {
      amount: string;
      currencyCode: string;
    };
  };
  images: {
    edges: Array<{
      node: {
        url: string;
        altText: string | null;
      };
    }>;
  };
}

interface ProductsResponse {
  products: {
    edges: Array<{ node: Product }>;
  };
}

export async function getProducts(first: number): Promise<Product[]> {
  const data = await shopifyClient.request<ProductsResponse>(GET_PRODUCTS_QUERY, { first });
  return data.products.edges.map((edge) => edge.node);
}
```

この関数は、指定した数の商品データを取得し、型安全な形で返します。

---

## ステップ4: 商品一覧ページの構築

`src/app/page.tsx`を更新して、商品一覧を表示します。`getStaticProps`を使用してビルド時にデータを取得します：

```tsx
import { getProducts } from '@/lib/shopify';
import Link from 'next/link';

interface Product {
  id: string;
  title: string;
  handle: string;
  priceRange: {
    minVariantPrice: {
      amount: string;
      currencyCode: string;
    };
  };
  images: {
    edges: Array<{
      node: {
        url: string;
        altText: string | null;
      };
    }>;
  };
}

interface HomeProps {
  products: Product[];
}

export async function getStaticProps() {
  const products = await getProducts(10); // 最初の10商品を取得
  return {
    props: {
      products,
    },
    revalidate: 60, // ISR: 60秒ごとに再生成
  };
}

export default function Home({ products }: HomeProps) {
  return (
    <main className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-6">商品一覧</h1>
      <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
        {products.map((product) => (
          <Link href={`/products/${product.handle}`} key={product.id}>
            <div className="border rounded-lg p-4 hover:shadow-lg transition">
              <img
                src={product.images.edges[0]?.node.url}
                alt={product.images.edges[0]?.node.altText || product.title}
                className="w-full h-48 object-cover rounded"
              />
              <h2 className="text-xl font-semibold mt-2">{product.title}</h2>
              <p className="text-gray-600">
                {parseFloat(product.priceRange.minVariantPrice.amount).toFixed(2)}{' '}
                {product.priceRange.minVariantPrice.currencyCode}
              </p>
            </div>
          </Link>
        ))}
      </div>
    </main>
  );
}
```

このコードは：
- `getStaticProps`で商品データを取得（ISRを有効化）。
- Tailwind CSSを使ってレスポンシブなグリッドレイアウトを作成。
- 各商品をカード形式で表示し、クリックすると商品詳細ページに遷移（後で実装）。

---

## ステップ5: 動作確認

1. `.env.local`にShopifyのエンドポイントとアクセストークンを設定。
2. 開発サーバーを起動（`npm run dev`）。
3. `http://localhost:3000`にアクセスし、商品一覧が表示されるか確認。

商品カードがグリッド形式で表示され、画像、タイトル、価格が正しく表示されていれば成功です。エラーがあれば、コンソールやShopifyのアクセストークンを確認してください。

---

## まとめと次のステップ

このエピソードでは、Shopify Storefront APIをNext.jsに統合し、商品一覧ページを構築しました。`graphql-request`を使ったAPIクライアントの設定や、`getStaticProps`によるデータ取得の方法を学びました。Tailwind CSSのおかげで、レスポンシブでモダンなUIも簡単に実現できました！

次回のエピソードでは、商品詳細ページ（PDP）を構築し、SEO最適化や画像の遅延読み込みを追加します。動的ルーティングやメタタグの設定も詳しく解説しますので、ぜひお楽しみに！

---

この記事が役に立ったと思ったら、ぜひ「いいね」を押して、ストックしていただければ嬉しいです！次回のエピソードもお楽しみに！