⚠️

ShopifyのLiquidからHydrogenへの段階移行 〜プロキシの利用〜

2024/12/25に公開

はじめに

こんにちは!株式会社ブロードエッジ・ウェアリンク CTOの高丸です。
今回は、Qiita Advent Calendar 2024の13日目の記事です。

今回は小ネタではありながらも、Shopifyの開発において非常に重要なポイントについてお話したいと思います。それは、ShopifyテーマのLiquidからHydrogenへの段階移行についてです。

Hydrogenへの段階移行

11日目の記事でも触れましたが、ShopifyはLiquidからHydrogenへの段階的な移行を推奨しています。公式ドキュメントでは、コンポーネントごとの段階的な移行パスが示されていますが、これは比較的シンプルなストア構成を想定していると考えられます。
https://shopify.dev/docs/storefronts/headless/hydrogen/migrate

実際のところ、我々のような構成を持つストアでは、状況はもう少し複雑です。
弊社の場合、カルテのシステムとShopifyの連携や、複数のShopifyアプリの利用など、すぐには移行できない要素が多数存在します。
おそらく、Shopifyを本格的に活用している多くのプロダクトも、同様の状況にあるのではないでしょうか。

このような状況下での移行アプローチとして、以前Shopifyの公式ドキュメントに記載されていた(現在は削除された)興味深い方法があります。
それは、Hydrogenをプロキシとして利用し、既存のオンラインストアにトラフィックを流すというものです。

現在のHydrogenは依然として開発途上にあり、このような複雑な移行シナリオへの対応は、主にコミュニティメンバーによって模索されている状況です。

GitHub Discussionsでも関連する議論は見られるものの、残念ながら活発な議論には至っていません。(重要な課題なのに!)
https://github.com/Shopify/hydrogen/discussions/993
https://github.com/Shopify/hydrogen-v1/discussions/1724

プロキシの利用

Hydrogenをプロキシとして利用してオンラインストアにトラフィックを流す方法は、確かに強引な手法です。しかし、現実的な移行シナリオを考えた場合、場合によっては必要となる選択肢の一つとなり得ます。

ただし、この方法には以下のようなリスクが伴うことを十分に認識しておく必要があります。

  • Shopifyのプラットフォーム上では、CloudFlareのキャッシュが広範囲に効いており、予期せぬ古いデータが返されてしまう可能性がある
  • Hydrogen(Oxygen)以外のIPアドレスから連続的なアクセスが発生すると、Shopify側でブロックされる可能性がある
  • コミュニティのコードであるため、セキュリティ面での公式な担保がない(公式ではない)
  • 旧オンラインストアの認証は別途対応が必要

弊社でもこのプロキシを利用した経験がありますが、これらのリスクを考慮し、非常に限定的な利用に留めていました。具体的には、以下の2点を重要な方針として掲げました。

  • 後続のリプレイスリリースを急ぎ、できるだけ早く移行を完了させること(プロキシ利用を恒久的な対応としないこと)
  • 特集ページ(pages)など、リスクが低く、かつキャッシュされても大きな問題が発生しない部分に限定して使用すること

公式ドキュメントから消えてしまったコードですが、以下のようなコードでした。

import type {ActionArgs, LoaderArgs} from '@shopify/remix-oxygen';

export async function action(args: ActionArgs) {
  return proxy(args);
}

export async function loader(args: LoaderArgs) {
  return proxy(args);
}

export async function proxy({context, request}: LoaderArgs | ActionArgs) {
  const {
    shop: {
      primaryDomain: {url},
    },
  } = await context.storefront.query(
    `#graphql
      query {
        shop {
          primaryDomain {
            url
          }
        }
      }
    `,
  );

  const {origin, pathname, search} = new URL(request.url);

  const customHeaders = request.headers;
  customHeaders.delete('content-length');

  const response = await fetch(url + pathname + search, {
    headers: customHeaders,
    method: request.method,
    body: request.body || undefined,
    redirect: 'manual',
  });

  const data = await response.text();

  const processedData = data
    .replace(
      /{不要なヘッダを置換するための正規表現}/gi,
      (match) => {
        // 置換処理
        return match;
      },
    )
    .replace(new RegExp(url, 'g'), origin);

  const status = /<title>(.|\n)*404 Not Found(.|\n)*<\/title>/i.test(data)
    ? 404
    : response.status;

  const headers = new Headers(response.headers);
  headers.set('content-type', 'text/html');
  headers.delete('content-encoding');

  if (headers.has('location')) {
    headers.set('location', headers.get('location')!.replace(url, origin));
  }

  return new Response(processedData, {status, headers});
}

具体的には、Remixのキャッチオールルート($.tsx)を利用し、Hydrogenのルーティングが設定されていないリクエストをすべて旧オンラインストアに転送するという方法です。(メタプログラミング的なアプローチですね)

もう一度言いますが、これはサンプルコードであり、弊社は一切責任を負いませんので、使用にはご注意ください。

さいごに

Shopifyの開発において、LiquidからHydrogenへの移行は避けては通れない道のりとなっています。特に、複雑な機能や連携を持つストアにとって、この移行は慎重に計画する必要があります。

プロキシを利用した段階移行は、確かにリスクを伴う手法ですが、適切な範囲で利用することで、より柔軟な移行戦略を実現できる可能性があります。

我々の経験が、同様の課題に直面している他のチームの参考になれば幸いです。Hydrogenの発展とともに、より標準的な移行パスが確立されることを期待しつつ、コミュニティとしても知見を共有していければと考えています。

Discussion