Claude CodeでShopifyをJSONテンプレートへ移行する — 1テンプレートずつ進める手順
Liquidテンプレートで組んだShopifyテーマを、JSONテンプレートとセクション構成へ移す手順です。併存できない制約、presets、theme check、プレビュー確認をClaude Codeの進め方と合わせて扱います。
Claude CodeでShopifyをJSONテンプレートへ移行する — 1テンプレートずつ進める手順
Liquidテンプレートで組まれたShopifyテーマをOnline Store 2.0のJSONテンプレートへ移すなら、Claude Codeには「テンプレートを1枚ずつ移し、そのたびに検証する」進め方を取らせるのが安全です。JSONテンプレートは、表示するセクションの一覧と設定値を持つデータファイルです。マークアップ本体は sections/ 側のLiquidファイルに移り、templates/ には並びと設定だけが残ります。
移行の難所は書き換えの量より、守るべき制約の多さにあります。同名のLiquidとJSONは併存できず、セクション側にも書くべき項目があります。これらを先にClaudeへ渡しておくと、手戻りはかなり減ります。日常のテーマ編集と shopify theme check の回し方はClaude CodeでShopifyテーマ開発を進める手順に書いています。ここでは、旧い構造から新しい構造へ移すときに固有の判断だけを扱います。
JSONテンプレートへ移すと何が変わるか
Shopifyの仕様では、Liquidテンプレートはセクションと組み合わせて使えません。セクションを使いたいテンプレートは、JSONテンプレートにする必要があります。Liquidテンプレートが標準のHTMLとLiquidをそのまま受け取るのに対し、JSONテンプレートは固定のスキーマを持つJSONだけを受け付けます。
違いを実務の目線で並べると、次のとおりです。
| 観点 | Liquidテンプレート | JSONテンプレート |
|---|---|---|
| 中身 | LiquidテンプレートHTMLとLiquidのマークアップ | JSONテンプレート使うセクションの一覧と設定値 |
| セクションの追加・並べ替え | Liquidテンプレートテーマエディターからはできない | JSONテンプレートマーチャントがエディターで操作できる |
| アプリのブロック | Liquidテンプレート対象外 | JSONテンプレートセクション内にアプリブロックを置ける |
| 上限 | Liquidテンプレート固定スキーマなし | JSONテンプレート1テンプレートに25セクション、1セクションに50ブロック |
Shopifyは、JSONテンプレートを使うとテーマエディターの性能が上がり、settings_data.json に溜まるデータも減ると説明しています。テーマ全体で持てるJSONテンプレートは、テンプレートタイプをまたいで1,000個までです。
つまり移行の目的は、見た目を変えることではありません。マーチャントが触れる単位を、テンプレートからセクションへ下ろすことです。この目的がぶれると、Claudeはマークアップを丸ごと1つのセクションへ移して終わりにしがちです。それでも動きますが、エディターで並べ替えられる余地は生まれません。
移す前から決まっている制約
制約は、最初にまとめてClaudeへ渡します。公式の記述から、移行の判断に直結するものを拾いました。
移行で守る5つの制約
同名のLiquidとJSONは併存できない
product.liquidが残っているとproduct.jsonは作れません。移すときは、JSONを作る手順とLiquidを外す手順を同じ作業単位に入れます。JSONにできないテンプレートがある
gift_card、robots.txt、agents.md、llms.txt、llms-full.txtはLiquidテンプレートのままにします。presetsが無いセクションは手で書く
エディターから追加できるセクションには、スキーマに
presetsが要ります。無いセクションは、JSONに手で書き足す扱いになり、エディターから削除できません。参照先のセクションはテーマ内に実在させる
JSONが指すセクション(アプリセクションを除く)がテーマに無いと、エラーになります。
IDは重複させない
sectionsのキーもorderの要素も、重複できません。orderに書くIDはsectionsに存在する必要があります。
ファイル名にも決まりがあります。JSONテンプレートの名前は、有効なテンプレートタイプに、代替テンプレート用のサフィックスを付けた形です。product.json や product.alternate.json がその例です。商品ごとに別のレイアウトを割り当てている旧テーマでは、product.alternate.liquid のようなファイルが並びます。これらの名前は、サフィックスも含めて変えないほうが無難です。サフィックスを変えると、既存の割り当てが外れるおそれがあります。
最初の依頼は棚卸し — 書き換えさせない
最初の作業では、Claudeに何も書き換えさせません。Claude Codeにはプランモードがあり、ファイルを読んで計画を出すだけで、承認するまで編集しない状態にできます。移行の入口に向いています。Shopify公式のLiquid用スキルや言語サーバーを足す場合は、liquid-lspとliquid-skillsの導入が参考になります。
claude --permission-mode planセッション中は Shift+Tab でも切り替えられ、ステータスバーに ⏸ plan mode on と出ます。この状態で、次のように頼みます。
templates/ 配下の .liquid を全部洗い出して、次の表を作って。
- ファイル名 / テンプレートタイプ / 代替テンプレートか
- 中で {% section %} を呼んでいるか(呼んでいるセクション名)
- JSONにできない種類(gift_card, robots.txt など)に当たるか
- 移す順番の提案(影響の小さいものから)
まだ何も編集しないこと。コマンドで補っておくと、Claudeの表を人間が照合しやすくなります。
ls templates
grep -rn "{% *section " templates layout
grep -rln "{% *render\|{% *include" templates{% section %} タグは、Liquidテンプレートの中でセクションを静的に差し込む書き方です。静的に描画したセクションは、マーチャントが削除も並べ替えもできません。公式もできるだけ避けるよう勧めています。棚卸しで見つかった {% section %} は、JSONテンプレートの sections と order に移す候補です。同じセクションを複数のファイルから静的に呼んでいる場合、実体は1つで、設定の変更は呼んでいる全ての場所に反映されます。移すと分離されるので、ここは仕様の変わる点として覚えておきます。
棚卸しの表が出たら、移す順番を決めます。404や固定ページのような単純なテンプレートから始めると、手順そのものの確認になります。商品ページのようにアプリの表示や分岐が絡むものは後に回します。
CLAUDE.mdに移行のルールを書く
移行は数十のテンプレートに同じ操作を繰り返すので、ルールをCLAUDE.mdへ固定します。公式の共通ワークフロー集にも、リファクタリングは「小さく、検証できる単位で進める」という助言があります。次はその考え方をShopifyの制約に当てはめた例です。
# Online Store 2.0 移行ルール
## 作業単位
- 1回の作業で移すテンプレートは1つだけ
- JSONを作る変更と、同名の .liquid を外す変更は同じコミットにする
- gift_card / robots.txt / agents.md / llms.txt / llms-full.txt は触らない
## セクション側の条件
- 新しいセクションは {% schema %} に name と settings を書く
- エディターから追加させたいセクションには presets も書く
- main 用のセクションは、テンプレートの中心となるオブジェクトを使う
(商品ページなら product オブジェクト)
## 完了条件
- shopify theme check --fail-level error が終了コード0
- templates/*.json の sections と order のIDが一致している
- 手で整形しない: config/settings_data.json, locales/*.json
## 禁止
- shopify theme push は実行しない
- JSON内の custom_css を書かない・書き換えないcustom_css の禁止は、公式の記述に基づきます。マーチャントがエディターで追加したCSSが、セクションデータの custom_css に保存されます。Shopifyは、テーマ開発者がこの設定を足したり、設定済みの値を編集したりしないよう求めています。
1テンプレートの移し方
1回の作業は、次の流れです。
テンプレート1枚を移す手順
- 1
マークアップをセクションへ切り出す
templates/product.liquidの本文を、sections/main-product.liquidに移します。ファイルの末尾に{% schema %}を付けます。 - 2
JSONテンプレートを作る
templates/product.jsonに、使うセクションの一覧と並びを書きます。 - 3
旧いLiquidを外す
templates/product.liquidを削除します。同名の併存はできないので、JSONを作った時点で外すのが前提です。 - 4
検証する
shopify theme checkを走らせ、JSONの参照とスキーマの誤りを拾います。
移す前のテンプレートが、次のような形だったとします。これは説明用の例で、実際のテーマの中身ではありません。
{%- comment -%} templates/product.liquid(移行前) {%- endcomment -%}
<div class="product">
<h1>{{ product.title }}</h1>
{% render 'product-gallery', product: product %}
{% form 'product', product %}
<button type="submit">{{ 'products.add_to_cart' | t }}</button>
{% endform %}
</div>
{% section 'product-recommendations' %}このマークアップを、そのままセクションへ移します。移行段階ではHTMLを作り変えず、置き場所だけを変えるのが肝心です。見た目の差が出たとき、原因が構造の移動か中身の変更かで切り分けられなくなるからです。
{%- comment -%} sections/main-product.liquid(移行後) {%- endcomment -%}
<div class="product">
<h1>{{ product.title }}</h1>
{% render 'product-gallery', product: product %}
{% form 'product', product %}
<button type="submit">{{ 'products.add_to_cart' | t }}</button>
{% endform %}
</div>
{% schema %}
{
"name": "Product"
}
{% endschema %}{% section 'product-recommendations' %} の行は、テンプレートからJSONへ移ります。templates/product.json は次のとおりです。
{
"sections": {
"main": {
"type": "main-product"
},
"recommendations": {
"type": "product-recommendations"
}
},
"order": [
"main",
"recommendations"
]
}type はセクションのファイル名から拡張子を除いた値で、キーの main と recommendations はテンプレート内で一意のIDです。IDに使えるのは英数字のみです。product-recommendations を静的な呼び出しからJSONの項目に変えた分、マーチャントはエディターからこのセクションを非表示にしたり、並べ替えたりできるようになります。
セクション側にも気をつける点があります。エディターで「セクションを追加」から選ばせたいなら、スキーマに presets を足します。
{% schema %}
{
"name": "Product recommendations",
"presets": [
{ "name": "Product recommendations" }
]
}
{% endschema %}presets が無いセクションも、JSONに書けば表示はできます。ただしエディターからは削除できません。どのセクションをマーチャントに触らせるかは、移行前に決めておく項目です。この判断はClaudeには任せきれません。店舗の運用を知っているのは人間だからです。
整合チェックはスクリプトにして毎回走らせる
併存の禁止は、Claudeの記憶に頼らず機械で見張れます。次のスクリプトは、同名のLiquidとJSONが並んでいないか、JSONが指すセクションのファイルが実在するかを確かめます。公式のルールを写した、自作の簡易チェックです。
#!/bin/bash
# scripts/check-os2.sh
status=0
for f in templates/*.json; do
[ -e "$f" ] || continue
if [ -e "${f%.json}.liquid" ]; then
echo "併存: $f と ${f%.json}.liquid"; status=1
fi
for t in $(jq -r '.sections[]?.type' "$f"); do
case "$t" in shopify://*) continue ;; esac
[ -e "sections/$t.liquid" ] || { echo "未実在: $f -> $t"; status=1; }
done
done
exit $statusアプリ由来のセクションは sections/ に実体を持たないため、shopify:// で始まる type を実在確認から外す想定にしています。これは例の割り切りで、アプリごとの書式は各アプリの説明に従います。
スクリプトの後に shopify theme check を回します。Theme Checkには、JSONテンプレート向けのチェックが揃っています。
| チェック | 重要度 | 見つけるもの |
|---|---|---|
JSONMissingSection | 重要度Error | 見つけるものJSONテンプレートかセクショングループが、存在しないセクションを参照している |
JSONMissingBlock | 重要度Error | 見つけるものJSONテンプレートが、存在しないブロックタイプを参照している |
JSONSyntaxError | 重要度Error | 見つけるもの不正なJSONのファイル |
ValidSchema | 重要度Warning | 見つけるもの{% schema %} タグの中の不正なJSON |
MissingTemplate | 重要度Warning | 見つけるもの存在しない render / section / include の参照先 |
ValidSchema はWarningなので、--fail-level error では失敗になりません。セクションへ切り出した直後は、スキーマのJSONを崩しやすい場面です。完了条件をerrorだけにするなら、warningの出力も目で見てもらう指示を足しておきます。
bash scripts/check-os2.sh && shopify theme check --fail-level errorこの2つを、1テンプレートごとのコミット前に通します。PostToolUse hookへ載せる方法は、冒頭で紹介したテーマ開発の記事にあるものがそのまま使えます。
プレビューで差を確かめる
theme checkが通っても、見た目が同じとは限りません。ローカルのテーマを開発テーマとしてアップロードして表示を確かめるのが、shopify theme dev です。すでに開発テーマがあると、ローカルのテーマで置き換えられます。
shopify theme dev --store your-store.myshopify.com移行前後の比較は、次の手順にすると差が見えやすくなります。
- 移行前のテーマで、対象ページのURLと表示を控える(商品ページなら、バリエーションのある商品と、無い商品の2種類)
- 1テンプレートを移して、同じURLを開いて見比べる
- 差があれば、セクション側のHTMLを疑う前に、
orderの並びとsettingsの値を見る
比較に使うページは、移す前にClaudeへ列挙させておくと抜けが減ります。テンプレートごとに「確認するURLの例」をメモさせる指示で足ります。
開発テーマとマーチャントの編集がぶつかる場合に備えて、--theme-editor-sync というフラグがあります。テーマエディターでの更新をローカルのテーマファイルへ同期する機能です。エディターで編集中のストアに対してJSONテンプレートを書き換えるときは、エディター側の変更を取り込んだうえで進めるのが筋です。JSONの衝突の扱いは、keep-local / keep-remote / abort から選べます。
JSONテンプレートは、エディターの操作でも書き換わるファイルです。手元で書き換えたものと、ストア側で変わったものが食い違うことがあります。移行の作業は、マーチャントが触らない時間帯か開発用のストアで行うほうが、衝突を避けられます。
並行して別の作業が走る場合は、claude --worktree で作業ツリーを分けられます。移行用のブランチを別ツリーに隔離しておけば、日常の修正と混ざりません。この機能はリポジトリに最低1つのコミットがあることが前提です。
つまずきやすい点
落とし穴
静的な {% section %} を残したままJSONにすると二重になる
Liquidテンプレートに残した {% section 'xxx' %} を、JSONの sections にも書くと、同じセクションを二重に描画することになります。Liquidの本文をセクションへ移すときは、テンプレート内の {% section %} 行をJSONに移して、本文からは消します。
落とし穴
mainのセクションが中心オブジェクトを持たない
JSONテンプレートを作るときは、そのテンプレートの中心機能を持つセクションも作る必要があります。公式の例では、コレクション一覧のテンプレートは collections オブジェクトを使うセクションを参照します。main に当たるセクションが空では、ページとして成立しません。
落とし穴
アプリのセクションを手で消してしまう
アプリが追加したセクションがJSONに入っていることがあります。Claudeが「未使用に見える」として削るのを防ぐため、CLAUDE.mdに「sections/ に対応するファイルが無い type は、アプリ由来の可能性があるので消さない」と書いておきます。
移行の進め方を一言でまとめる
移行は「1テンプレート、1コミット、1回のプレビュー」の繰り返しです。フレームワークの移行を1ルートずつ進めるApp Router移行の手順や、ReduxからZustandへのストア単位の移行と、段階を小さく保つ考え方は共通です。併存禁止やpresetsのようなShopify側の制約をCLAUDE.mdとスクリプトに落とし込めば、Claudeは構造の移動に集中できます。エディターで何を触らせるかというマーチャントとの取り決めだけは、人間の判断として残ります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- shopify.dev/docs/storefronts/themes/architecture/templates/json-templa…
- shopify.dev/docs/storefronts/themes/architecture/templates
- shopify.dev/docs/storefronts/themes/architecture/templates/liquid-temp…
- shopify.dev/docs/storefronts/themes/architecture/sections
- shopify.dev/docs/storefronts/themes/tools/theme-check/checks
- shopify.dev/docs/api/shopify-cli/theme/theme-dev
- code.claude.com/docs/en/common-workflows