1. Top
  2. ブログ一覧
  3. Shopify Customer account UI extensions: Full-page extensionsの紹介
Eコマース

Shopify Customer account UI extensions: Full-page extensionsの紹介

公開日

2024.12.27

Shopify Customer account UI extensions: Full-page extensionsの紹介のサムネイル

ShopifyのCustomer Account UI Extensionsには、画面の一部にカスタム要素を表示する「Inline extensions」や、注文データを活用したインタラクションを追加する「Order action extensions」など多彩なextensionが存在します。

今回紹介するのは、「Full-page extensions」です。これは文字どおり顧客アカウントのページ全体を、新しいページへと置き換えたり、追加の独自ページを作成したりするためのextensionとなります。 これにより、既存のShopifyアカウントページの枠にとらわれず、企業独自のブランディングや高度な機能を実装しやすくなります。

Full-page extensionsとは?

Full-page extensionsは、以下のような特徴を持ちます。

  • 新規ページの追加・既存ページの置き換え
    通常、Shopifyの顧客アカウント画面は「注文履歴」「住所管理」「支払い情報」など、標準的なページ構成となっています。Full-page extensionsを使うと、これらのページをまるごと拡張して独自のUIにする、もしくは追加のページを作成して顧客に新たな行動フローを提供することが可能です。
  • ルーティングの自由度が高い
    Full-page extensionsでは、Shopifyが用意する/account/xxxのようなルート(パス)に対して独自のページを割り当てることができます。これにより、顧客が自然な導線でページを行き来できるようになります。
  • よりリッチなインタラクション
    先述のInline extensionsやOrder action extensionsでは、UIの一部分または特定のアクションだけをカスタマイズするのが中心でした。一方で、Full-page extensionsはページ全体を制御できるため、フォーム入力や画像・動画の埋め込みなど、豊富なインタラクションを実現しやすくなります。

販売業者のニーズは多様であり、一部のユースケースは既存の顧客アカウント ページのextensionターゲットには適合しません。
たとえば、ロイヤルティ プログラム、ウィッシュリスト、サブスクリプション、返品、交換などの一般的なユースケースのextensionを作成するには、別のページが必要です。このような場合は、customer-account.page.renderターゲットを使用してFull-page extensionsを構築します。Full-page extensionsは、ヘッダーの下、フッターの上に新しいページで UI をレンダリングします。

代表的なユースケース

  • 独自の会員ランクページ
    プロフィール情報やポイント履歴、会員ステータスなどをまとめて表示し、リワードを受け取る手順を案内する専用ページを作ることができます。 リワード管理や顧客ロイヤルティの強化にも有効です。
  • FAQ・サポートポータルの統合
    購入情報と連携する形で、FAQや問い合わせページをまるごと顧客アカウント内に組み込むケースがあります。顧客が購入履歴を確認しながら、関連するサポート情報を自然に参照・問い合わせできるようにすれば、サポート負荷の軽減と顧客満足度向上が期待できます。
  • B2B専用ダッシュボード
    B2B取引では、顧客の会社情報や法人向け注文履歴などを別ページでまとめたいことが少なくありません。Full-page extensionsを使えば、B2Bの利用者向けに特化したダッシュボードを実現でき、通常のD2Cページとは異なる情報設計・UIを持つページを提供できます。
  • 購入後の次のアクションを促すページ
    商品のセットアップガイドやレビュー投稿促進、追加注文を誘導するためのランディングページを、顧客アカウント内に直接埋め込むことも考えられます。購入完了直後に遷移させる動線を張れば、顧客のロイヤルティやLTV向上に役立ちます。

Inline・Order action extensionsとの違い

  • Inline extensions
    顧客アカウント ページの既定領域へ要素を差し込む拡張。元のページ構造を大きく崩さずにUIカスタマイズができるのが特徴。
  • Order action extensions
    注文データに紐づいたアクションを追加し、顧客が能動的に問題報告や再購入依頼などを実行できるようにする拡張。特定の操作フローに注目したカスタマイズが可能。
  • Full-page extensions
    ページ全体を新たに構成・作成できる拡張。顧客アカウントのルーティング自体も自由度が高いため、独自のUI/UXを一気に実現できる反面、ある程度の設計負荷はかかります。

実装手順の紹介

ここでは、お気に入り商品一覧ページを作成するユースケースを例に、Full-page extensionsの導入手順を紹介します。

仕組み

Full-page extensionsには固有の URL があります。Full-page extensionsの URL にリンクする他のextensionを作成できます。デフォルトでは、Full-page extensionsは直接リンクを許可します。販売者は、Full-page extensionsのリンクをオンライン ストアまたは顧客アカウントのナビゲーション メニューに追加できます。また、ページ URL をコピーして任意の場所に追加することもできます。

直接リンクを禁止する

すべてのFull-page extensionsが直接リンクに適しているわけではありません。たとえば、extensionで特定の注文のコンテキストが必要な場合は、customer-account.order.page.renderターゲットを拡張するのが最適です。これにより、販売者がextensionにリンクできなくなります。
また、注文アクションを作成したり、注文ステータスページを拡張してFull-page extensionsにリンクしたりすることもできます。

extensionが特定の注文のコンテキストを必要としないが、販売者がリンクできないようにする必要がある場合は、 extensionの構成ファイルshopify.extension.tomlallow_direct_linking = falseを使用して直接リンクを禁止できます。

[extensions.targeting.capabilities]
allow_direct_linking = false

制限事項

現在、販売業者と開発ショップは、顧客アカウントのヘッダーナビゲーションにフルページの拡張 URL を追加できません。この機能は、将来のリリースでサポートされる予定です。
各extensionは、extensionターゲットを 1 回だけ拡張できます。複数のextensionを作成することで、複数の新しいページを作成できます。ユースケースごとに 1 つのページを専用にすることをお勧めします。

extensionの作成

まずは、extensionを作成します(React + TypeScriptの例)。

shopify app generate extension --template=customer_account_ui --name="My wishlist"--flavor=typescript-react

ターゲットの設定

次に、どのルート(パス)へページを追加するかをshopify.extension.tomlで指定します。 既存のprofileなどを上書きする場合もあれば、カスタムルートを新規作成することも可能です。

以下は新しいページ/account/custom-dashboardを作成する例です。

[[extensions]]
type = "ui_extension"
name = "My wishlist"
handle = "my-wishlist"

[[extensions.targeting]]
module = "./src/FullPageExtension.tsx"
target = "customer-account.page.render" # 顧客アカウントページ全体を拡張するターゲット

[extensions.capabilities]
api_access = true

UIの作成

新規ページのUIを構築します。src/OrderStatusBlock.tsxsrc/FullPageExtension.tsxにファイル名を編集し、ページのコンポーネントを作成します。
Customer Account APIを使って、顧客のお気に入り商品一覧を取得し、表示する例を以下に示します。今回は簡単にメタフィールドに商品を保存する形で実装します。

import { useEffect, useState } from "react";
import {
  reactExtension,
  useApi,
  Grid,
  BlockStack,
  TextBlock,
  Button,
  Image,
  Page,
  ResourceItem,
  SkeletonImage,
  SkeletonText,
} from "@shopify/ui-extensions-react/customer-account";

export default reactExtension("customer-account.page.render", () => (
  <FullPageExtension />
));
interface Product {
  id: string;
  title: string;
  onlineStoreUrl: string;
  featuredImage: {
    url: string;
  };
  priceRange: {
    minVariantPrice: {
      amount: number;
      currencyCode: string;
    };
    maxVariantPrice: {
      amount: number;
      currencyCode: string;
    };
  };
}

function FullPageExtension() {
  const { i18n, query } = useApi<"customer-account.page.render">();
  const [wishlist, setWishlist] = useState<Product[]>([]);
  const [loading, setLoading] = useState(false);
  const [removeLoading, setRemoveLoading] = useState({
    id: null,
    loading: false,
  });

  async function fetchWishlist() {
    setLoading(true);
    try {
      const customerQuery = {
        query: `query {
          customer {
            metafield(namespace: "product", key: "wish_list") {
              jsonValue
              }
          }
        }`,
      };
      const result = await fetch(
        "shopify://customer-account/api/2024-10/graphql.json",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(customerQuery),
        },
      );
      const { data } = await result.json();
      console.log(data);
      const wishlistIds = data.customer.metafield.jsonValue || [];

      // 商品情報をフェッチする関数
      const fetchProductDetails = async (id: string) => {
        const productQuery = `query {
            product(id: "${id}") {
              id
              title
              onlineStoreUrl
              featuredImage {
                url
              }
              priceRange {
                minVariantPrice {
                  amount
                  currencyCode
                }
                maxVariantPrice {
                  amount
                  currencyCode
                }
              }
            }
          }`;
        const productResult = await query<{ product: Product }>(productQuery);
        return productResult.data.product;
      };

      // 全商品情報を並列でフェッチ
      const wishListProducts = await Promise.all(
        wishlistIds.map((id) => fetchProductDetails(id)),
      );
      console.log(wishListProducts);

      setLoading(false);
      setWishlist(wishListProducts || []);
    } catch (error) {
      setLoading(false);
      console.log(error);
    }
  }

  async function deleteWishlistItem(id: string) {
    // 削除処理のモック
    setRemoveLoading({ loading: true, id });
    return new Promise<void>((resolve) => {
      setTimeout(() => {
        // 実際はmetafieldの値を更新するリクエストを送信する
        setWishlist(wishlist.filter((item) => item.id !== id));

        setRemoveLoading({ loading: false, id: null });
        resolve();
      }, 750);
    });
  }

  useEffect(() => {
    fetchWishlist();
  }, []);

  return (
    <Page title="お気に入りリスト">
      <Grid columns={["fill", "fill", "fill"]} rows="auto" spacing="loose">
        {loading && (
          <ResourceItem loading>
            <BlockStack spacing="base">
              <SkeletonImage
                inlineSize="fill"
                aspectRatio={1}
                blockSize="fill"
              />
              <BlockStack spacing="none">
                <SkeletonText inlineSize="base" />
              </BlockStack>
              <SkeletonText inlineSize="small" />
            </BlockStack>
          </ResourceItem>
        )}
        {!loading &&
          wishlist.length > 0 &&
          wishlist.map((product) => {
            return (
              <ResourceItem
                loading={loading}
                key={product.id}
                action={
                  <>
                    <Button kind="primary" to={product.onlineStoreUrl}>
                      商品を見る
                    </Button>
                    <Button
                      kind="secondary"
                      loading={
                        removeLoading.loading && product.id === removeLoading.id
                      }
                      onPress={() => {
                        deleteWishlistItem(product.id);
                      }}
                    >
                      削除
                    </Button>
                  </>
                }
              >
                <BlockStack spacing="base">
                  <Image source={product.featuredImage.url}></Image>
                  <TextBlock emphasis="bold">{product.title}</TextBlock>
                  <TextBlock appearance="subdued">
                    {i18n.formatCurrency(
                      product.priceRange.minVariantPrice.amount,
                      {
                        currency:
                          product.priceRange.minVariantPrice.currencyCode,
                      },
                    )}
                  </TextBlock>
                </BlockStack>
              </ResourceItem>
            );
          })}
        {!loading && wishlist.length === 0 && (
          <TextBlock>
            お気に入りリストが空っぽです!気になる商品をどんどん追加してみましょう!
          </TextBlock>
        )}
      </Grid>
    </Page>
  );
}

お気に入りページへのリンクを追加

inline extensionsを作成して、顧客プロフィールページにお気に入りページへのバナーリンクを追加します。

以下のコードを実行して作成します。

shopify app generate extension --template=customer_account_ui --name="Upsell banner" --flavor=typescript-react

作成したextensionを以下のように設定します。
shopify.extension.tomlを編集して、customer-account.profile.page.renderターゲットにextensionを追加します。

[[extensions.targeting]]
module = "./src/ProfileBlockExtension.tsx"
target = "customer-account.profile.block.render" # 顧客プロフィールページのブロックに拡張を追加するターゲット

src/OrderStatusBlock.tsxsrc/ProfileBlockExtension.tsxにファイル名を変更し、バナーコンポーネントを作成します。

import {
  reactExtension,
  Link,
  Card,
  InlineStack,
  Text,
} from "@shopify/ui-extensions-react/customer-account";

export default reactExtension("customer-account.profile.block.render", () => (
  <BlockExtension />
));

function BlockExtension() {
  return (
    <Card padding>
      <InlineStack inlineAlignment="center" spacing="tight">
        <Text>あなたの部屋をお気に入りのグッズでいっぱいにしましょう。</Text>
        <Link to="extension:my-wishlist/">お気に入りの商品を見る</Link>
      </InlineStack>
    </Card>
  );
}

リンクのURLが特殊であることに注目してください。to="extension:my-wishlist/"と指定することで、Full-page extensionsへのリンクを作成することができます。

アクセススコープの設定

Customer Account APIを使って顧客オブジェクトにアクセスするため、アクセススコープcustomer_read_customersを設定します。

アプリルートのshopify.app.tomlを編集して、以下のように設定します。

[access_scopes]
scopes = "customer_read_customers"

追加したら権限を更新するためにアプリを一度デプロイします。インストールがまだの場合は、インストールしてください。

shopify app deploy

顧客のメタフィールド定義

お気に入り商品を保存するためのメタフィールドを顧客に追加します。管理画面の設定 > カスタムデータ から、以下のように設定します。

Custom Account APIからアクセスできるよう、読み取り権限を設定します。削除処理を入れる場合は書き込み権限も追加します。

メタフィールドの設定画面

定義したら、自身の顧客アカウントにお気に入り商品を追加してみましょう。

お気に入り商品の追加

動作確認

以上で、お気に入りページの作成と、プロフィールページへのリンクを追加する手順が完了しました。

以下のコマンドを実行して、ローカルサーバーを起動し、動作を確認します。

shopify app dev

コンソールにPreview URLが表示されるので、ブラウザでアクセスして動作を確認します。

Developer Consoleが表示されるので作成したupsell-bannerのPreview linkをクリックします。

Developer Consoleの画面

バナーが表示されているのがわかりますね。リンクをクリックします。

お気に入りページのバナー

無事登録したお気に入り商品が表示されました。削除ボタンを押すと、削除処理が実行されリストから商品が消えることが確認できます。

お気に入り商品一覧

お気に入りリストに関連するおすすめ商品を表示すれば、さらに顧客の購買意欲を高めることができるでしょう。

運用・実装上のポイント

  • ページの遷移導線
    カスタムページを作るだけでは顧客が気づきにくい場合があります。顧客アカウントのトップページやメインメニューにリンクを表示し、自然な導線を確保することが大切です。
  • UI/UX設計の自由度が高いが、ガイドラインを意識
    Full-page extensionsはページ全体を覆えるため、デザインや要素配置を自由に決められますが、Shopifyのスタイルガイドや顧客が慣れ親しんだ操作性を考慮し、「らしさ」と「使いやすさ」のバランスをとりましょう。
  • アナリティクス・A/Bテストの活用
    カスタムページを用意したら、きちんとどれくらいのアクセスがあり、どのような操作が行われているかをトラッキングすることも重要です。ページ設計やコンテンツに応じて改善を重ねましょう。
  • アクセス制御とセキュリティ
    会員ランクやポイントなど、機密性の高い情報を扱う場合は、アクセス権限の管理やAPIリクエストのセキュリティ対策を徹底します。顧客固有のデータに対して誤表示が起きないよう注意が必要です。
  • 他のExtensionとの連携
    Full-page extensionsで作ったページ内にInline extensionsを差し込んだり、Order action extensionsを組み合わせることも可能です。複合的に活用して顧客アカウント体験を向上させましょう。

Full-page extensions導入で得られるメリット

  • ブランド体験の一体化
    Shopify標準のUIにとどまらず、ブランドごとに最適化されたページを作ることで、より一貫性のある体験を提供できます。
  • 機能拡張の柔軟性
    ページ全体の設計が可能で、ステップフォームや複雑な条件分岐、外部API連携を多用するUIにも対応しやすくなります。
  • 顧客との接点の強化
    ログイン後、顧客アカウント内で独自キャンペーンや会員制度を積極的にアピールでき、再購入やアップセルを促す機会を拡大できます。

まとめ

Full-page extensionsは、Shopifyの顧客アカウントをさらに進化させるための強力な手段です。 ページ単位でカスタマイズできるため、シンプルなUI変更から高度なダッシュボードの実装まで、多彩なニーズに応えられます。一方で、自由度が高い分、導線設計やデザイン・セキュリティ面の考慮が欠かせません。

Inline extensionsやOrder action extensionsと併用しながら、顧客ごとに最適なフローを提供し、ビジネス目標の達成につなげてください。

参考リンク