FRONTEND長文・約 73,429

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

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

Shopify Themeで外部CMSを使いたいけど、クライアントサイド取得だとパフォーマンスが...。Webhook + SSRで事前にHTMLを生成し、Shopifyに同期するアプローチを解説します。

この記事のポイント

  • Shopify Theme(Liquid)はサーバーサイドで外部 API を呼べないため、CMS 連携がクライアントサイドになりパフォーマンスと SEO が悪化
  • Hydrogen に全面移行する手もあるが、既存テーマからの移行コストが大きい
  • Webhook + SSR で事前に HTML を生成し Shopify Article として保存する構成で、サーバーサイド処理の制約を回避
  • 外部 CMS(Contentful)のリッチな編集体験を活かしながら、Core Web Vitals と SEO を犠牲にしない

はじめに

「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の編集体験とパフォーマンスの両立を目指してみてはいかがでしょうか。

参考文献

SHARE
鶴田 篤広
ABOUT THE AUTHOR
鶴田 篤広
ソフトウェアエンジニア · クレインテック株式会社
CRANE TECH — TECHNICAL ADVISORY

システム開発・DXでお悩みではありませんか?

「何から始めればいいかわからない」「社内にIT人材がいない」そんな企業様に、課題の整理からシステムの構築・運用まで伴走支援いたします。