• ホーム
  • ブログ
  • Shopify Theme × Contentful:外部CMS連携のパフォーマンス問題を解決する

Shopify Theme × Contentful:外部CMS連携のパフォーマンス問題を解決する

鶴田 篤広のプロフィール画像

鶴田 篤広

ソフトウェアエンジニア

作成日:

ShopifyContentfulHeadless CMSReactSSREC開発
Shopify Theme × Contentful:外部CMS連携のパフォーマンス問題を解決する - Shopify Themeで外部CMSを使いたいけど、クライアントサイド取得だとパフォーマンスが.....

TL;DR

  • Shopify Theme(Liquid)はサーバーサイドで外部APIを呼び出せないため、外部CMSとの連携はクライアントサイドになりパフォーマンス・SEOに悪影響
  • Hydrogenなら自由にサーバーサイド処理ができるが、既存テーマからの移行コストが大きい
  • Webhook + SSRで事前にHTMLを生成し、Shopifyの記事として保存するアプローチで解決
  • 外部CMS(Contentful)のリッチな編集体験を活かしつつ、パフォーマンスを犠牲にしない

はじめに

「Shopifyの標準ブログエディタが物足りないから、ContentfulやmicroCMSを使いたい...」 「でも、Shopify ThemeからAPIを叩くとクライアントサイドになってしまう...」 「SEOやCore Web Vitalsのスコアが心配...」

こんな悩みを抱えていませんか?

Shopify Theme(Liquid)は優れたテンプレートエンジンですが、サーバーサイドで外部APIを呼び出す機能がありません。これが外部CMSとの連携を難しくしている根本的な原因です。

Shopify Themeの技術的制約

Liquidからは外部APIを呼べない

Shopify ThemeのLiquidテンプレートはサーバーサイドでレンダリングされますが、Liquid内から外部のREST APIやGraphQL APIを呼び出すことはできません

つまり、外部CMS(Contentful、Sanity、microCMS等)のコンテンツを表示したい場合、以下のような実装になります:

// クライアントサイドでAPIを叩くしかない
fetch('https://cdn.contentful.com/spaces/xxx/entries/yyy')
  .then(res => res.json())
  .then(data => {
    document.getElementById('blog-content').innerHTML = renderContent(data);
  });

クライアントサイド取得の問題点

この方法には深刻な問題があります:

問題 影響
初期表示が遅い ページロード後にAPIコール→レンダリングとなり、FCP/LCPが悪化
SEOに不利 検索エンジンのクローラーがコンテンツを認識しにくい
レイアウトシフト コンテンツが遅れて表示され、CLSスコアが悪化
ローディング表示 ユーザーに「読み込み中...」を見せることになる

ブログ記事のようなコンテンツ重視のページで、これらの問題は致命的です。

Headless構成なら解決できるが...

Shopify Themeを使わず、独自のフロントエンドを構築するHeadless構成であれば、この問題は発生しません。

Headless Shopifyの実現方法はいくつかあります:

  • Hydrogen: Shopify公式のReactフレームワーク(Remixベース)
  • Next.js + Storefront API: 独自にNext.jsなどでフロントエンドを構築
  • その他のフレームワーク: Nuxt、Astro、SvelteKitなど任意の技術スタック

いずれの方法でも、サーバーサイドで自由に外部API(Contentful等)を呼び出せます。

しかし、Headless構成への移行は:

  • 既存のShopify Themeを捨てることになる
  • テーマカスタマイズやアプリ連携の資産が使えなくなる(多くのアプリはTheme前提)
  • フロントエンドをゼロから構築する必要がある

既存のShopify Themeで運用中のサイトにとっては、現実的でないケースも多いでしょう。

解決策:Webhook + SSRによる事前HTML生成

本記事で紹介するアプローチは、Shopify Themeの制約を回避しつつ、外部CMSを活用する方法です。

基本的な考え方

  1. 外部CMS(Contentful)でコンテンツを作成・編集
  2. 公開時にWebhookで自社サーバーに通知
  3. サーバーサイドでCMSからコンテンツを取得し、ReactでSSR
  4. 生成したHTMLをShopify Admin API経由で記事として保存
  5. Shopify Themeは通常通り記事を表示するだけ

ポイントは、Shopify Themeがレンダリングする時点では、すでにHTMLが完成しているということです。クライアントサイドでのAPI呼び出しは一切不要になります。

なぜContentfulを選んだか

外部CMSとしてContentfulを選んだ理由は以下の通りです:

  • Rich Textエディタの充実: 画像、動画、カスタムコンポーネントの埋め込みが可能
  • Images API: URLパラメータで画像のリサイズ、フォーマット変換、品質調整が可能
  • Webhook機能: コンテンツの公開・更新時に自動で通知を送信
  • TypeScriptサポート: 型定義が充実しており、開発体験が良い

代表的なHeadless CMSには、Contentful、Sanity、Strapi、microCMSなどがありますが、同様のアプローチはどのCMSでも実現可能です。

アーキテクチャ概要

実装したシステムの全体像は以下の通りです:

ダイアログ1

実装のポイント

1. Contentful Clientの初期化

まず、Contentfulからコンテンツを取得するクライアントを設定します。

import { createClient } from 'contentful';

const client = createClient({
  space: process.env.CMS_SPACE_ID ?? '',
  environment: process.env.CMS_ENVIRONMENT ?? 'master',
  accessToken: process.env.CMS_ACCESS_TOKEN ?? '',
  host: 'preview.contentful.com', // Preview APIで下書き確認可能
});

export const getCMSPost = async (postId: string) => {
  const result = await client.getEntries({
    content_type: 'blogPost',
    'sys.id': postId,
    include: 3, // 3階層までネストされた参照を解決
  });

  return convertCMSItemToPost(result.items[0]);
};

include: 3パラメータがポイントです。これにより、記事に埋め込まれた著者情報、関連画像、関連記事などのネストされた参照を一度のAPIコールで取得できます。

2. Webhookハンドラーの実装

Contentfulでコンテンツが公開されたとき、Shopifyに自動同期するWebhookハンドラーを実装します。

import type { RequestHandler } from 'express';

type CMSWebhookTopic =
  | 'ContentManagement.Entry.create'
  | 'ContentManagement.Entry.publish'
  | 'ContentManagement.Entry.unpublish';

export const postWebhookHandler: RequestHandler = async (req, res) => {
  const topic = req.headers['x-contentful-topic'] as CMSWebhookTopic;
  const entryId = req.body.sys.id;

  switch (topic) {
    case 'ContentManagement.Entry.create':
    case 'ContentManagement.Entry.update':
      await saveArticle(entryId);
      break;

    case 'ContentManagement.Entry.publish':
      await updatePublishStatus(entryId, true);
      break;

    case 'ContentManagement.Entry.unpublish':
      await updatePublishStatus(entryId, false);
      break;
  }

  res.json({ message: 'OK' });
};

ContentfulのWebhookはx-contentful-topicヘッダーでイベント種別を通知します。これを使って、作成・更新・公開・非公開を適切にハンドリングします。

3. Rich TextからReactコンポーネントへの変換

ContentfulのRich Textフィールドは、構造化されたJSONとして返されます。これをReactコンポーネントに変換します。

import { documentToReactComponents, Options } from '@contentful/rich-text-react-renderer';
import { BLOCKS, INLINES } from '@contentful/rich-text-types';

const renderOptions: Options = {
  renderNode: {
    // 外部リンクの処理
    [INLINES.HYPERLINK]: ({ content, data }) => {
      const text = content[0].value;
      const { uri } = data;
      return (
        <a
          href={uri}
          target="_blank"
          rel="noreferrer nofollow"
        >
          {text}
        </a>
      );
    },

    // 見出しにアンカーリンクを自動付与
    [BLOCKS.HEADING_2]: ({ content }) => {
      const text = content[0].value;
      return (
        <>
          <a id={text} className="anchor" />
          <h2>{text}</h2>
        </>
      );
    },

    // レスポンシブ画像の生成
    [BLOCKS.EMBEDDED_ASSET]: ({ data }) => {
      const asset = data.target;
      return (
        <img
          src={`https:${asset.fields.file.url}?w=1024&fm=webp&q=70`}
          srcSet={`
            https:${asset.fields.file.url}?w=640&fm=webp&q=70 640w,
            https:${asset.fields.file.url}?w=1024&fm=webp&q=70 1024w
          `}
          sizes="(min-width: 1024px) 1024px, 100vw"
          alt={asset.fields.title}
        />
      );
    },
  },
};

export const PostBody = ({ content }) => {
  return (
    <div className="post-body">
      {documentToReactComponents(content, renderOptions)}
    </div>
  );
};

このアプローチの利点は、コンテンツ編集者はWYSIWYGエディタで自由に編集でき、開発者は出力されるHTMLを完全にコントロールできることです。

4. 画像の自動最適化

ContentfulのImages APIを活用して、レスポンシブ画像を自動生成します。

export const generateResponsiveImage = (asset, preset = 'content') => {
  const sizes = {
    content: [320, 640, 1024, 1280],
    hero: [640, 1024, 1920],
    thumbnail: [200, 400],
  };

  const srcSet = sizes[preset]
    .map(width => {
      const url = `https:${asset.fields.file.url}?w=${width}&fm=webp&q=70`;
      return `${url} ${width}w`;
    })
    .join(', ');

  return {
    src: `https:${asset.fields.file.url}?w=${sizes[preset][0]}&fm=webp&q=70`,
    srcSet,
    sizes: '(min-width: 1024px) 1024px, 100vw',
  };
};

WebP形式への自動変換と、デバイスに応じた適切なサイズの配信により、画像のファイルサイズを大幅に削減できます。

5. Shopifyテーマとの連携

Shopify側では、Metafieldを使ってCMS管理の記事を識別し、条件分岐で表示を切り替えます。

{%- if article.metafields.custom.is_cms_managed -%}
  {%- comment -%}
    CMS管理記事用のスタイルを読み込み
  {%- endcomment -%}
  {{ 'cms-article.css' | asset_url | stylesheet_tag }}

  <div class="cms-article">
    {{ article.content }}
  </div>
{%- else -%}
  {%- comment -%}
    通常のShopify記事
  {%- endcomment -%}
  <article class="standard-article">
    {{ article.content }}
  </article>
{%- endif -%}

この方法なら、既存のShopify記事とCMS管理の記事を同じテーマで共存させられます。段階的な移行も可能です。

このアプローチで得られるメリット

パフォーマンス問題の解消

クライアントサイドでのAPI呼び出しが不要になるため:

  • FCP/LCPの改善: ページロード時点でコンテンツが存在
  • CLSの改善: レイアウトシフトが発生しない
  • SEOに有利: クローラーがコンテンツを正しく認識
指標 クライアントサイド取得 本アプローチ
FCP ページ読込 + API + レンダリング ページ読込のみ
LCP 大幅に遅延 通常通り
CLS シフト発生 シフトなし
SEO クローラーが認識しにくい 完全に認識

外部CMSの編集体験を活用

  • WYSIWYGエディタで直感的に編集可能
  • 画像のドラッグ&ドロップでアップロード
  • プレビュー機能で公開前に確認
  • 複数人での同時編集が可能
  • カスタムコンポーネントの埋め込み

既存のShopify Theme資産を維持

  • テーマカスタマイズがそのまま使える
  • Shopifyアプリとの連携も維持
  • 段階的な移行が可能(記事単位でCMS管理に切り替え)

導入時の課題と解決策

CSSスコーピング問題

課題: React生成のHTMLとShopifyテーマのスタイルが衝突する

解決策: CSS Modulesを使用し、クラス名をコンポーネント単位でスコーピング。esbuildのプラグインでComponentName_className形式の命名規則を適用。

Webhookの信頼性

課題: ネットワーク障害でWebhookが失敗する可能性がある

解決策: べき等な操作として実装。「存在確認→なければ作成、あれば更新」のロジックにより、リトライしても安全。

まとめ

Shopify Themeを使っていて、外部CMSを導入したいけどパフォーマンスが心配...という方は、本記事で紹介したWebhook + SSRによる事前HTML生成のアプローチを検討してみてください。

このアプローチが向いているケース

  • Shopify Themeで運用中だが、ブログ編集機能に不満がある
  • 外部CMSのリッチな編集体験を使いたい
  • パフォーマンス(Core Web Vitals)やSEOを犠牲にしたくない
  • Hydrogenへの全面移行は現実的でない

向いていないケース

  • 新規構築でゼロから始められる → Headless構成(Hydrogen、Next.js等)を検討
  • リアルタイム性が必要なコンテンツ → 別のアプローチが必要
  • 連携システムを構築・運用するリソースがない

Shopify Themeの「サーバーサイドで外部APIを呼べない」という制約は、工夫次第で回避できます。既存のテーマ資産を活かしながら、外部CMSの編集体験とパフォーマンスの両立を目指してみてはいかがでしょうか。

参考文献

こんなお悩みはありませんか?

1

システムの改修・刷新

古いシステムを使い続けているが、そろそろ限界を感じている

2

技術の相談相手がいない

社内にエンジニアがおらず、技術的な判断を相談できる人がいない

3

新規サービスを小さく始めたい

アイデアはあるが、まずは最小限の形で試してみたい

4

業務の効率化・自動化

手作業やExcel管理から脱却し、業務をシステム化したい

5

AIを活用したい

ChatGPTなどのAIを業務に取り入れたいが、どう始めればいいかわからない

このようなお悩みをお持ちの企業様に、
クレインテックが伴走支援いたします。

初回のご相談・お見積もりは無料です。

この記事をシェア

クレインテックに相談する

お客様と一緒に課題を整理し、小さく始めて育てる「共創型開発」を行っています。

「こんなシステムは作れる?」「費用感を知りたい」など、どんな段階でもお気軽にご相談ください。

初回のご相談は無料です。

お問い合わせ