通勤中のスマホで気に入った商品をカートに入れて、家に帰ってからパソコンで買おうとしたら、カートが空だった。ネットショップを使っていて、この経験がない人のほうが少ないと思います。
売る側から見ると、この場面はとても厄介です。買う気があったお客様が、商品を探すところからやり直しになっているのに、ストアの管理画面には何の記録も残りません。カゴ落ちメールの対象にもなりません。「なぜか買われなかった」という数字だけが残ります。
この記事では、その取りこぼしを設定 1 回で塞ぐ Shopify アプリ、シンプルカート同期|お手軽デバイス間カート自動同期を、管理画面の画面ごとに全部解説します。実際にインストールした画面をすべて撮影しているので、導入前にどんな画面を触ることになるのかが分かります。あわせて、同じことをコードで自作した場合にどこで詰まるのかも書きました。
ログインしているお客様のカートを、スマホ・タブレット・パソコンの間で自動的に同じ状態に保つアプリです。お客様が押すボタンはひとつも増えません。
Shopify のカートは、ブラウザの cookie に保存されたカートトークンで管理されています。カートの中身そのものはサーバー側にありますが、「どのカートか」を指し示す鍵がブラウザにあるため、ブラウザが変われば別のカートとして扱われます。
つまり、次のようなことが起こります。
スマホで入れた商品は、パソコンで開いたときには入っていません。
同じパソコンでも、Chrome で入れた商品は Safari では見えません。
シークレットウィンドウを閉じると、そのカートには二度と戻れません。
見落とされがちなのが、ログインしていても引き継がれないという点です。Shopify の顧客アカウントは、注文履歴や配送先住所は保持しますが、カートの中身は保持しません。「アカウントにログインしているのだから、当然引き継がれるはず」と思っているお客様ほど、空のカートを見て戸惑うことになります。
やっていることは、次の 2 つだけです。
ログイン中のお客様がカートを変えたら、その中身をアカウントに紐づけて保存する。
同じアカウントで別の端末を開いたら、保存しておいた中身をカートへ書き戻す。
保存も書き戻しも、お客様には見えないところで進みます。増える表示は、書き戻しが起きたときの 1 行のメッセージだけです。

このアプリには「カートを保存する」ボタンがありません。「前回のカートを復元しますか」という確認ダイアログも出ません。ログインしている状態でストアを開けば、それだけで前の続きから買い物が再開されます。
買い物の途中で確認を挟むと、そこで一度手が止まります。止まった瞬間に「やっぱりやめよう」と離脱するお客様は一定数いるので、確認を出さない設計そのものが売上に効きます。

未ログインの訪問者には、このアプリの JavaScript そのものが配信されません。テーマ側の Liquid が、ログイン状態を見てスクリプトの読み込み自体を出し分けているためです。
ログインしていない訪問者にとっては、アプリが入っていないストアと表示も速度もまったく同じになります。カート同期はログイン顧客のための機能なので、それ以外の人に負担をかけない作りにしてあります。
ここは実装で差が出るところです。スマホとパソコンを同時に開いて両方でカートを触ると、素直に作った場合は古い状態を読んだ側が、後から新しい保存を上書きします。片方で足した商品が、もう片方の操作で黙って消えることになります。
このアプリは保存するデータに更新時刻を持たせていて、自分が読み込んだ版より新しい保存があれば書き込みません。ぶつかったときは最大 5 回まで自動でやり直します。同じ商品が両方のカートにある場合は、数量の多いほうを残します。足し算はしません。
保存した時点では買えた商品が、別の端末で開いたときには売り切れていることがあります。このアプリは、買えない商品をカートから除いたうえで、除いたこと自体をお客様に伝えます。
伝える内容は件数だけで、商品名は出しません。家族と端末を共有している場合などを考えて、カートの中身が画面に出ないようにしてあります。

保存先は、そのストアの顧客メタフィールドだけです。アプリ提供者のサーバーにカートの内容を送ることはありません。
保存するのは商品と数量だけで、商品名・価格・画像の URL は保存しません。古い情報を表示しないためと、持つデータを最小限にするためです。
インストール時に Shopify が表示する権限の画面でも、このアプリは顧客の氏名・メールアドレス・電話番号・住所を要求していません。位置情報や IP アドレスも取りません。

管理画面の表示言語は 20 言語から選べます。お客様の画面に出るメッセージも同じく 20 言語ぶん用意してあり、こちらはストアフロントの言語設定に合わせて自動的に切り替わります。

これは仕組み上どうにもなりません。カートの持ち主を特定できないと、どのアカウントに保存すればよいか決められないからです。
ゲスト購入が中心のストアでは、効果が出る場面がそもそも限られます。導入するなら、ログインを促す導線とセットで考えてください。「ログインすると、どの端末からでも続きから買えます」という一文をカートページに置くだけでも、会員登録の理由になります。

同期の対象は、カートに入っている商品と数量だけです。割引コード、注文メモ、カート属性は端末をまたぎません。
キャンペーン中は、コードを入力する画面を端末ごとに通る前提で導線を組んでおく必要があります。商品を入れ直す手間はなくなるので、残るのはコードの入力だけになります。
このアプリの管理画面には統計がありません。保存されたカートが何件の注文につながったか、いくらの売上になったかを見る画面は用意していません。
「入れた効果を数字で説明したい」という要件があるなら、そこは弱点になります。同じジャンルの他のアプリには、直近 30 日の同期件数や売上を出すものがあります。
同期の間隔、ログアウト時のカートの扱い、復元後にページを再読み込みするかどうか。こうした挙動を選ぶ設定は用意していません。
「入れたら動く」ことを優先した設計なので、細かく制御したい場合には物足りないはずです。逆に、決めることが何もないという点は、担当者が変わっても運用が壊れない利点でもあります。
料金は月額 $3.99 で、年払いにすると $39.99 です。年払いのほうが 16% 安く、実質 2 か月分が無料になる計算です。インストールから 7 日間は無料で試せます。

プランは 1 つだけで、ストアの Shopify プランや注文件数による段階分けはありません。同期できるカートの件数にも上限を設けていません。
Shopify App Store から入れます。手順は他のアプリと変わりません。
まず Shopify の管理画面で、左下にある設定を開きます。

「アプリと販売チャネル」に進み、そこから Shopify App Store を開きます。

検索窓に「シンプルカート同期」と入れると、アプリが見つかります。

詳細ページの左側にあるインストールのボタンから導入します。

最後に権限の確認画面が出ます。このアプリが求めるのは、テーマの情報を読む権限と、保存カートを読み書きするための顧客情報の権限だけです。注文データには触れません。
インストールが終わると、サイドバーにアプリが並びます。開くと現れる画面は「設定」の 1 ページだけで、その中にカードが 4 枚縦に並んでいます。

上から順に、テーマにカート同期を追加するカード、カート同期の仕様を説明するカード、言語設定のカード、おすすめアプリのカードです。日々の運用で開く必要はほとんどありませんが、お客様から問い合わせを受けたときに読む場所として用意しています。
以下、カードごとに何ができるのかを見ていきます。
管理画面のいちばん上にあるカードです。インストールしただけでは同期は始まりません。 テーマ側でアプリを有効にする操作が 1 回だけ必要で、その入口がここになります。

置かれている操作は次の 3 つです。
テーマを選択 … 有効にしたいテーマを選ぶ欄です。初期状態では公開中のテーマが選ばれています。ストアに複数のテーマがある場合は、ここで切り替えます。
テーマに追加 … 押すと新しいタブでテーマエディタが開きます。開いた先で保存すると有効になります。
テーマをプレビュー … 追加の操作をせずに、そのテーマのアプリ設定画面だけを開きます。今どのテーマで有効になっているかを確かめたいときに使います。
カードの説明文にも書かれているとおり、テーマのバージョンによっては選択欄が空になることがあります。その場合はテーマエディタを直接開いて有効にしてください。
2 枚目のカードです。このアプリがどう動くのかを 6 つの見出しにまとめてあります。設定項目ではなく読み物ですが、この記事でいちばん読む価値があるのはここだと思います。「カートが同期されない」という問い合わせを受けたとき、ほとんどの原因はこの中に書いてあります。

同期される条件の欄には 3 つ挙がっています。お客様がログインしていること、テーマでアプリが有効になっていること、そして「ログイン前に同じブラウザで入れた商品は、ログインしたときに保存済みのカートと合流する」ことです。3 つ目は問い合わせで誤解されやすいところで、ログイン前の商品が消えるわけではないと伝えられると話が早く終わります。
反映のタイミングの欄では、切り替えた時点で最新のカートを取りに行くこと、画面を開いたまま放置している間は反映されないこと、反映まで数秒かかることが書かれています。動作確認をするときは、必ずページを読み込み直してください。開いたまま待っていても何も起きません。

同期する範囲は、カートに入っている商品の情報だけです。割引コード・注文メモ・カート属性は対象外だと明記してあります。
復元されない・数量が調整される場合の欄が、いちばん行数が多いところです。在庫切れや販売停止の商品は戻らないこと、数量の上限がある場合は実際に入った数量になること、マーケットや B2B の設定で買えない商品も除かれること。そして価格を保存していないため、復元した時点のストアの価格が適用されることも書かれています。セール前に入れたカートをセール後に開けば、セール後の価格になります。
復元を行わない画面として挙がっているのは、チェックアウト、顧客アカウント、フォームの入力中の 3 つです。住所を入力している最中にカートが書き換わると、入力途中の内容が消えることがあります。それを避けるために、これらの画面では書き戻しを止めています。
複数の端末で同時に変更した場合は、両方をできる限り合流させ、同じ商品は数量の多いほうを残します。「厳密に後から行った操作が勝つ動作は保証していない」と正直に書いてあるのは、この種のカードでは珍しいと思います。
3 枚目のカードです。ここだけが、動きを変えられる唯一の設定になります。

置かれている操作は 2 つです。
言語を選択 … 表示したい言語を選ぶ欄です。日本語・英語のほか、チェコ語、デンマーク語、ドイツ語、スペイン語、フィンランド語、フランス語、イタリア語、韓国語、ノルウェー語、オランダ語、ポーランド語、ポルトガル語(ブラジル)、ポルトガル語(ポルトガル)、スウェーデン語、タイ語、トルコ語、中国語(簡体字)、中国語(繁体字)の 20 言語から選べます。
変更する … 押すとその場で表示が切り替わります。ページの再読み込みは要りません。
英語を選ぶと、管理画面はこのようになります。

同じ画面を日本語で開くと、当然ですが日本語になります。

ここで 2 点、知っておくと役に立つことがあります。
ひとつは、選んだ言語がブラウザにだけ記憶されることです。設定はブラウザのローカルストレージに保存されるので、別のパソコンやブラウザで開くと選び直しになります。裏を返せば、同じストアをスタッフごとに違う言語で使えるということでもあります。
もうひとつは、ここで切り替わるのが管理画面の言語だけだという点です。お客様の画面に出るメッセージの言語は、ストアフロント側の言語設定に従います。管理画面を英語にしても、日本語のストアフロントには日本語のメッセージが出ます。

4 枚目のカードです。同じ開発元が出している他のアプリが一覧で並びます。右上のバツ印で閉じられます。

このアプリの機能には関係しない領域ですが、「カートまわりを整えたい」「購入手順を分かりやすく伝えたい」といった次の課題に、そのまま使えるものが並んでいます。

英語表示にすると、こちらの一覧も英語になります。

これは常に出ているものではなく、日本語表示のときだけ現れる案内です。カード形式ではなく、黄色い警告のバナーとして表示されます。

テーマに追加を押しても自動追加がうまくいかない場合に、テーマエディタから手動で有効にする方法を案内しています。テーマの作りによっては自動追加が通らないことがあるため、その逃げ道を最初から示してあります。
管理画面の説明が済んだので、実際に有効にする手順を見ていきます。ここが唯一の必須作業です。
テーマに追加のボタンを押すと、新しいタブでテーマエディタが開き、左側に埋め込みアプリのパネルが表示されます。

右側のトグルを ON にして、右上の保存を押せば完了です。

行を展開すると、設定項目がないことが確認できます。

表示位置や色を選ぶ項目は用意していません。トグルを ON にすること自体が、このアプリの唯一のスイッチです。

ここで気をつけたいことが 2 つあります。
ひとつは、テーマに追加を押したときにテーマエディタがホームページを開いた状態で立ち上がることです。プレビューがホームページになるというだけで、有効にしたアプリはテーマ全体で動きます。商品ページやカートページに個別で追加する作業は要りません。
もうひとつは、有効にするテーマを間違えないことです。ON と OFF はテーマごとに分かれています。公開中ではないテーマで ON にしても、お客様の画面では何も起こりません。 しかもエラーは出ないので、有効化し忘れは気づきにくい失敗です。テーマを複製して公開し直したあとは、有効化のやり直しが必要になることがあります。
ここからはお客様側の話です。マーチャントが操作する場面はありませんが、何が起きているかを知っておくと問い合わせに答えられます。
お客様が商品をカートに入れると、その約 1 秒後に内容が保存されます。続けて操作した場合も、遅くとも 5 秒後には必ず保存されます。細かく操作するたびに通信すると無駄が多いので、少し待ってからまとめて送る作りです。

別の端末で開いたときは、保存されているカートを取りに行って書き戻します。同じ端末で短時間に何度もページを移動した場合は、60 秒間は取得結果を使い回して、無駄な通信を減らしています。

書き戻しが起きたときは「他の端末で更新されたカート内容に同期しました。」という 1 行が表示されます。確認を求めるものではなく、何が起きたかを伝えるだけの表示です。

在庫切れなどで書き戻せなかった商品があると、件数だけを伝えるメッセージが出ます。「在庫切れのため、◯件の商品を追加できませんでした。」と「在庫が少ないため、◯件の商品は数量を減らして追加しました。」の 2 種類です。


導入を検討するとき、いちばん気になるのはここだと思います。
保存先は、そのストアの顧客メタフィールドです。アプリ提供者のサーバーにも、外部のデータベースにも、カートの内容は置いていません。
保存の形は次のようになっています。
顧客 1 人につきカートは 1 件です。新しい内容で上書きしていくので、過去のカートは残りません。
保存するのは商品と数量、そして更新時刻です。商品名・価格・画像の URL は保存しません。
保存できるデータの上限は 128KB です。通常のカートであれば、まず届きません。
アプリ専用のメタフィールドとして保存しているため、Shopify の管理画面の顧客ページには表示されません。
最後の点は補足が要ります。アプリが所有するメタフィールドは、管理画面の顧客ページのメタフィールド欄には現れません。実際に顧客ページを開いても、保存カートは見えません。店側から、お客様のカートの中身を一覧で覗く画面は存在しないということです。
保持期間は設けていません。顧客 1 人につき 1 件を上書きし続けるだけなので、古いカートが溜まり続けることはありません。お客様のアカウントが削除されたときと、アプリをアンインストールしたときには、保存カートも削除されます。

ここまで読んで「これくらいならテーマのカスタマイズでできそう」と感じた方もいると思います。実際、途中までは書けます。どこまで書けて、どこで行き止まりになるのかを順に見ていきます。
テーマの Liquid には、ログイン中の顧客オブジェクトが渡ってきます。未ログインなら空になるので、スクリプトの読み込みごと出し分けられます。
{% if customer %}
<script>
window.myCartSync = {
customerId: {{ customer.id | json }},
cartUpdatedAt: null,
};
</script>
<script src="{{ 'cart-sync.js' | asset_url }}" defer></script>
{% endif %}
未ログインの訪問者にはタグそのものが出力されないので、無関係なお客様に JavaScript を配らずに済みます。ここまでは素直に書けます。
Shopify のストアフロントには、カートの中身を返す JSON のエンドポイントがあります。ログイン判定を通ったあと、まずは現在のカートを読みます。
const getCart = async () => {
const res = await fetch('/cart.js', { headers: { Accept: 'application/json' } });
const cart = await res.json();
return cart.items.map((item) => ({
id: item.variant_id,
quantity: item.quantity,
}));
};
商品名や価格まで持つと、復元したときに古い情報を表示してしまうので、バリエーション ID と数量だけに絞っています。ここも問題なく動きます。
問題は、取り出した中身をどこに置くかです。素直に思いつくのはブラウザの保存領域ですが、これは端末をまたぎません。
// これは同じブラウザの中でしか効かない
localStorage.setItem('saved-cart', JSON.stringify(items));
localStorage はブラウザごとに分かれているので、スマホで保存した内容はパソコンからは読めません。やりたかったことがそのまま実現できないわけです。
顧客に紐づけて保存するには、顧客メタフィールドへ書く必要があります。ところがメタフィールドの書き込みは Admin API の担当で、ストアフロントの JavaScript からは呼べません。管理用のアクセストークンをテーマに埋め込めば技術的には動きますが、そのトークンはページのソースを開けば誰でも読めます。ストアの注文も顧客も自由に操作できる鍵を公開することになるので、選択肢になりません。
結局、自分のサーバーを 1 枚挟むことになります。Shopify にはそのための仕組みとして App Proxy が用意されていて、ストアのドメイン配下のパスへの通信を、自分のサーバーへ転送してくれます。
const saveCart = async (items) => {
await fetch('/apps/cart-sync/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});
};
転送されたリクエストには署名が付いてくるので、サーバー側でそれを検証してから、Admin API でメタフィールドを書きます。ここまで作ってようやく、端末をまたいで保存できる状態になります。つまり、テーマのカスタマイズだけでは完成しません。 サーバーを用意して動かし続ける前提になります。
保存ができたとして、次は書き戻しです。ここで多いのが、追加のエンドポイントをそのまま使ってしまう間違いです。
// 危ない例。いま入っているカートに「足して」しまう
await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: savedItems }),
});
このエンドポイントは加算であって、置き換えではありません。すでに 2 個入っている商品を数量 2 で書き戻すと 4 個になります。置き換えたいなら、いったんカートを空にしてから入れ直す必要があります。
await fetch('/cart/clear.js', { method: 'POST' });
await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: savedItems }),
});
ただしこの書き方にも問題が残ります。在庫切れの商品が 1 つでも混ざっていると、追加のリクエスト全体が失敗します。カートを空にした直後に失敗するので、お客様のカートは空のまま残ります。1 件ずつ入れて、失敗した件数を数え、その件数をお客様に伝える作りにしないと、事故になります。
サーバーを立てて、署名を検証して、1 件ずつ書き戻して。ここまでやっても、次の問題が残ります。
同時に 2 台で触られたときの上書き。 更新時刻か版番号を持たせて、自分が読んだ版より新しければ書かない仕組みが要ります。ぶつかったときのやり直しも必要です。
チェックアウト中の書き戻し。 住所を入力している最中にカートが変わると、入力途中の内容が消えることがあります。書き戻してよい画面かどうかの判定が要ります。
買えない商品の扱い。 在庫切れだけでなく、マーケットや B2B の設定で買えない商品も除く必要があります。
多言語のメッセージ。 「◯件を追加できませんでした」といった文言を、ストアの言語ぶん用意することになります。
保存の頻度。 操作のたびに送ると通信が増えます。少し待ってまとめて送る仕組みが要ります。
どれも「作れないもの」ではありませんが、どれも間違えてもエラーが出ないものです。動いているように見えて、たまにカートが巻き戻る。その報告を受けても再現できない。この種の不具合を追いかける時間を考えると、月額 $3.99 は安いほうだと思います。
同じジャンルのアプリを実際に開発ストアへ入れて比べました。ここでは 4 本を取り上げます。

パーシステントカート ‑ デバイス間同期(Customer First focus)は、2016 年公開でこの分野ではもっとも歴史があります。月額 $4.99 から。管理画面は英語のみですが、レビュー欄には 8 年近く使い続けているという声が並びます。初期設定は 3 段階で、テーマで有効にしたあと、通知用のメールアドレスを確認し、無料の同期テストを依頼する流れになっています。

Ecto (Casper) Persistent Cart(Outis)は、カートを 90 日間保存すると明記しているアプリです。月額 $15 からと高めですが、直近 30 日の保存件数・同期件数・そこから生まれた注文と売上を管理画面で見られます。加えて、顧客ごとの最新カートを管理画面から検索して中身を見られるのが特徴です。電話やメールで接客するストアとは相性がよい反面、それだけの情報がアプリ側に渡っているということでもあります。

Persistent Cart Recovery(Magefan)は、この 4 本で唯一の完全無料アプリです。ただし当方の開発ストアでは、インストール後に管理画面が 3 回とも読み込めず、設定内容までは確認できませんでした。要求する権限には氏名・メールアドレス・電話番号・住所が含まれていて、今回比べた中ではもっとも広い範囲でした。

Firecart: Persistent Cart Sync(Uptek)は、月額 $9.99 の単一プランです。4 本の中でもっとも設定が具体的で、同期後にページを自動で再読み込みするか、未ログイン中に入れた商品をログイン時に統合するか、ログアウトしたときカートをどうするかを選べます。開発ストアでは無料で、本番ストアが公開されるとプランに加入するまでカート保持が無効になる旨が管理画面に明示されます。
こうして並べると、カート同期という機能自体はどれも同じで、差が出るのは周辺だと分かります。整理すると次のようになります。
管理画面が日本語になるのは、シンプルカート同期だけでした。他の 3 本は英語のみか、確認できませんでした。
統計を見られるのは他の 3 本で、シンプルカート同期にはありません。効果を数字で説明したい場合は他を選ぶことになります。
顧客の氏名・メールアドレス・電話番号・住所を要求しないのは、シンプルカート同期だけでした。閲覧元の位置や端末の情報を求めるアプリもある中で、ここは差が出た部分です。
月額はシンプルカート同期の $3.99 がもっとも安く、無料で使えるのは Magefan の 1 本だけです。
Shopify のカートは、ログインしていても端末をまたぐと引き継がれません。標準機能では埋められない穴で、単価が高いストアや卸売のストアほど、そこで失っているものが大きくなります。
シンプルカート同期は、その穴を設定 1 回で塞ぐアプリです。この記事で見てきたとおり、管理画面で触るのはテーマを選んで追加するところと、表示言語を選ぶところだけです。テーマ拡張の設定項目にいたっては 1 つもありません。
決めることが少ないぶん、細かく制御したい場合には向きません。統計もありません。それでも、カートの中身をストアの外に出さず、お客様に一切の操作を求めず、日本語の管理画面で完結する。この 3 つを同時に満たすものは、今回比べた範囲では他にありませんでした。
料金は月額 $3.99、年払いなら $39.99 です。7 日間の無料体験があるので、まずは自分のストアで動かしてみるのが早いと思います。

同じ開発元が出している、購入導線まわりのアプリを紹介します。どれも設定を絞った作りで、入れてすぐ使えます。
シンプル検索0件対策|おすすめ商品レコメンド・関連商品表示 … 検索して何も見つからなかったお客様に、おすすめの商品を出せます。カート同期と同じく、黙って離脱していく人を減らすためのアプリです。
シンプルタブ表示切り替え|お手軽コンテンツ出し分け … どのページにもコンテンツタブを追加できます。商品説明や配送の案内を、縦に長くせず整理して見せられます。
シンプル追従ボタン|お手軽フローティングボタン … 画面に追従するボタンを置けます。カートやお問い合わせへの導線を、どのページからでも 1 タップにできます。
シンプル Google Maps 表示|お手軽マップ埋め込み … 埋め込みコードを貼るだけで、ストアフロントに地図を表示できます。実店舗を持つストア向けです。