Shopifyのストアフロントが最大29%高速化 ― Liquidテーマ開発者が気をつけるべき3点

Shopifyのストアフロントが最大29%高速化 ― Liquidテーマ開発者が気をつけるべき3点

2026年9月22日、Shopifyのパフォーマンス専門ブログ「Performance @ Shopify」に「Liquid storefronts now start loading 29% faster」という記事が公開されました。

結論から言うと、マーチャント側の作業は一切不要で、対象のページは自動的に速くなっています。ただしテーマのコードを触る人(テーマ開発者・カスタマイズ担当者)は、書き方次第でこの恩恵を丸ごと逃す、あるいは既存のスクリプトが壊れる可能性があります。この記事では、何が変わったのか・対象範囲・気をつけるべき点を整理します。

何が変わったか ― TTFB最大29%高速化の仕組み

まず数字から。全ストアフロントのページビューを対象に測定した結果、中央値ストアのTTFB(Time to First Byte/サーバーが最初の1バイトを返すまでの時間)が、p75で約29%、p90で21%短縮されました。より重要な指標であるFCP(First Contentful Paint/最初のコンテンツが描画されるまでの時間)とLCP(Largest Contentful Paint/最大コンテンツの描画完了までの時間)も、2〜6%改善しています。「より重要」とShopify自身が書いているのは、TTFBは技術者向けの指標だが、FCP・LCPは購入者が実際に体感する速さだからです。

仕組みはシンプルです。Liquidのページは大きく2つの部分でできています。

  • <head> ― meta タグ・スタイルシートのリンク・フォントのプリロード・少数のスクリプトなど、ほぼ静的なマークアップ。レンダリングに数ミリ秒しかかからない
  • テンプレート内のセクション群 ― 処理時間の大部分がここで消費される

これまでは、両方のレンダリングが完了してから初めてブラウザに送信されていました。ブラウザは<head>が届くまで何もできず、届いて初めてスタイルシートやスクリプトの存在に気づき、ダウンロードを開始していたのです。

アップデート後は、<head>のレンダリングが終わった時点で、テンプレートの残りがまだ処理中でもすぐにブラウザへ送信されます。ブラウザはその時点で<head>を解析し、接続を開いてスタイルシートやフォントのダウンロードを始められます。残りのコンテンツが届く頃には、最初の描画に必要なリソースはすでにダウンロード中か完了しているという流れです。

💡 HTML自体は変わりません。2つに分けて送られた部分をつなげれば、これまでと同じ1つのドキュメントになります。あくまで「いつ届くか」が変わっただけで、「何が届くか」は変わっていません。

なお、この改修は数十ファイルにまたがる規模でしたが、探索とコーディングの大部分をAIコーディングエージェントが担当し、複数チームのエンジニアが数週間かけてレビューした、とShopify自身が明かしています。「AIがスコープを扱いやすくし、最終的な品質は人間が担保した」という位置づけです。

対象は一部のページだけ ― JSONテンプレート限定

「ほとんどのストアフロントページはストリーミング対応済み」とありますが、全ページが対象ではありません。テーマ側で条件を満たす必要があります。

条件 内容
JSONテンプレートからレンダリングされている .liquid テンプレートはまだ対象外
レイアウトの<head>内で {{ content_for_header }} が「プレーンな出力タグ」になっている フィルターなし・{% if %}や{% capture %}で囲まれていない・スニペットに隠されていない・変数に代入されていない

.liquidテンプレートが対象外な理由も明快です。Liquidテンプレートはレンダリングの途中でレイアウトを差し替えたり、レイアウト側が後から読む変数を代入したりできるため、「ここで安全に切って送ってよい」というポイントが存在しません。JSONテンプレートは構造があらかじめ確定しているため、この切り分けが可能になっています。

⚠️ プレビューテーマ・プレビューバー・テーマエディタ内ではストリーミングされません。効果を確認したいときは、必ず公開中のライブテーマで見る必要があります。テーマエディタでの表示速度を見て「速くなっていない」と判断しないよう注意してください。

Shopifyは「対象を広げる作業を継続中」としており、今後は.liquidテンプレートを含め対象ページが増えていく見込みです。最新の対象条件は shopify.dev の該当ページで随時更新されるとのことです。

なぜ Dawn より Horizon の方が恩恵が大きいか

TTFBの改善はどの対象ストアでも同じように得られますが、それが実際の初回描画(FCP・LCP)の速さにどれだけ結びつくかはテーマ次第です。理由は単純で、ブラウザは「すでに受け取った分」しか先読みしてダウンロードできないからです。メインのスタイルシートが{{ content_for_header }}より下に置かれていると、レスポンスの後半(=テンプレートのレンダリングが終わった後)にならないと届かず、先行して送られた前半には meta タグ程度しか実利がありません。

この差が、公式テーマの Dawn と Horizon で対照的に表れました(中央値ストア・p75)。

指標 Dawn Horizon
TTFB 25% 29%
FCP 3% 8%
LCP 2% 4%

TTFBの改善幅はほぼ同じなのに、Horizonの方がFCPで約3倍、LCPで約2倍の恩恵を得ています。理由は配置の違いです。

  • Dawn:{{ content_for_header }}を<head>の早い位置に置き、base.cssやフォント宣言、コンポーネント別のスタイルシートはその下で読み込んでいる
  • Horizon:レンダリングをブロックするリソースのほぼすべてを{{ content_for_header }}より前で定義している

Horizonでは、テンプレートがまだレンダリング中の段階で、初回描画に必要なリソースがすでにブラウザの手元に渡っています。ストリーミングという土台は同じでも、それを活かせるかどうかはテーマの<head>の書き方で決まるということです。

テーマを触る人が気をつけるべき3点

「クリティカルなリソース定義を、できる限り{{ content_for_header }}より前に移動する」のがShopifyの推奨ですが、機械的に移動すると壊れる箇所があります。公式ガイド「Load first-paint resources before content_for_header」から、実務で踏みやすい3点を挙げます。

① content_for_header を「プレーンな出力タグ」に保つ

ストリーミングは、{{ content_for_header }}を<head>内のプレーンな出力タグとして検出できることに依存しています。次のような加工をすると、それだけで対象から外れます。

  • フィルターを適用する({{ content_for_header | filter }})
  • {% if %}や{% capture %}で囲む
  • 変数に代入してから出力する({% assign var = content_for_header %})
  • 別のスニペットの中に押し込める

レイアウトのカスタマイズで「一手間加えたくなる」場所ですが、ここだけは触らずそのまま出力する形を保つ必要があります。

② Shopify.* を参照するインラインスクリプトを前に出さない

Shopifyグローバルオブジェクトは{{ content_for_header }}の出力の中で初期化されます。そのため、これより前の位置に置いた同期スクリプトがShopify.*を参照すると、初期化前にパーサーが実行してしまいReferenceErrorで落ちます。

✗ 動かない ✓ 動く
window.themeRoot = Shopify.routes.root; window.themeRoot = {{ routes.root_url | json }};

値をJavaScript側でShopifyオブジェクトから拾うのではなく、Liquid側で値を取得してJSONとして埋め込む形に変えれば、位置を移動しても壊れません。DawnにあるShopify.designModeの判定も、同様に{% if request.design_mode %}のようなLiquid側の判定に置き換えられます。

③ CSSの詳細度(specificity)の逆転に注意

{{ content_for_header }}は、セクション用のスタイルシートやチェックアウトボタン用のスタイルなど、Shopifyが注入するCSSも出力します。詳細度が同じセレクタが競合した場合、CSSは「後に書かれた方」が勝つため、スタイルシートの読み込み位置を変えると勝敗が入れ替わります。

  • 移動前:{{ content_for_header }} → base.css の順 = base.cssが後発なので、同等詳細度ならbase.cssが勝つ
  • 移動後:base.css → {{ content_for_header }} の順 = 立場が逆転し、Shopify側のセクションCSSが勝つ

「.card { padding: 1rem }と指定したはずが効かなくなった」というような症状が出たら、詳細度の逆転を疑います。対処は該当セレクタの詳細度を上げる(クラスを足す等)ことです。

💡 移動してよいもの/悪いもの(ガイドより抜粋)
移動できる:<meta>・<title>・stylesheet_tagで読むベーススタイル・font_face宣言・preload_tag・{% style %}内のCSS変数・importmap・type="module"のスクリプト・async外部スクリプト
移動できない:Shopify.*を同期参照するインラインスクリプト・{% stylesheet %}タグ(セクションのコンパイル待ちが必要)

なお「コンテンツが届かなければ何も描画されない」という原則自体は変わっていないため、Liquidのベストプラクティス(不要なループを避ける、render呼び出しを絞る等)は引き続き重要です。画面上部のコンテンツを外部APIからJavaScriptで取得しているテーマは、そのリクエストをDOM構築完了前に送るようにすると、テンプレートのレンダリングと並行して走らせられます。

補足:TTFBの意味が変わったことに注意

計測ツールを使う人向けの注意点も1つ。ストリーミングが有効になったページでは、TTFBは「サーバー側の処理時間」を正しく表さなくなります。ブラウザは<head>が届いた時点を最初の1バイトとしてカウントするため、テンプレートの重い処理がまだ裏側で続いていても、TTFBだけを見ると速く見えてしまうのです。

Shopifyはこのギャップを認めた上で、当面の代替手段を2つ挙げています。

  • Theme Inspector for Chrome ― サーバー側のLiquidレンダリングをプロファイルするツール。ストリーミングの影響を受けないため、ループやrender呼び出しの最適化効果はここで確認できる
  • responseEnd - finalResponseHeadersStart ― ドキュメントの最初のバイトから最後のバイトまでの時間。「最初の送信後、サーバーがどれだけ処理を続けていたか」の代理指標として使える(計測ガイドにDevTools・WebPageTest・Lighthouseでの見方あり)

パフォーマンス計測を定点観測しているストアは、「TTFBが急に良くなった」=「サーバー処理が速くなった」と早合点しないよう、この点を踏まえておくとよさそうです。

まとめ

今回のアップデートは、マーチャントにとっては純粋な恩恵です。対象条件を満たしてさえいれば、何もしなくてもTTFBが縮み、購入者が体感する速さ(FCP・LCP)も多少なりとも改善します。

一方で、テーマのコードに手を入れる立場からは、次の3点を持ち帰っておくとよいはずです。

  • {{ content_for_header }}はフィルター・条件分岐・変数代入をせず、そのまま出力し続ける。そうしないとストリーミングの対象から外れる
  • 恩恵の大きさはテーマの<head>の書き方で決まる。クリティカルなCSS・フォントをcontent_for_headerより前に置けているテーマほど、FCP・LCPへの跳ね返りが大きい(Horizonの例)
  • リソースを前に移動する際は、Shopify.*参照とCSS詳細度の2点を必ず確認する。機械的に移動すると、動いていたスクリプトが壊れたり、上書きされていたはずのスタイルが逆に上書きしてしまったりする

Dawn・Horizonをベースにカスタマイズしているテーマは多いはずなので、まずは自分のテーマの<head>で{{ content_for_header }}がどの位置にあり、その前後に何が並んでいるかを一度確認しておくと、この先の対象拡大にもスムーズに乗れそうです。

参考リンク

Back to blog