買い切り納品・納品後は店舗が自分で更新できる多言語デジタルメニュー。SaaS化しない。販売者側に月額費用・DB管理・アカウント管理が残らない構成。
1店舗 = 1つの独立したGitHubリポジトリ = 1つの納品物
products.json(gitの履歴がそのまま変更履歴になる)images/(ブラウザ側でWebP圧縮してから保存)/
├── menu.html 公開メニュー(誰でも閲覧、ログイン不要)。products.jsonをfetchして表示
├── admin.html 店舗スタッフ専用の管理画面(本番、GitHub REST APIで実データを更新)
├── demo-admin.html 営業デモ用の管理画面(localStorageのみ、実データには一切触れない)
├── menu-demo.html demo-admin.htmlと連動する公開メニューのデモ版
├── qr.html サンプル用QRコード表示ページ
├── products.json 商品データ(下記「products.json仕様」参照)
├── images/ 商品写真(WebP形式)
├── README.md このファイル(技術者向け)
└── docs/
└── SETUP.md 店舗納品用のセットアップ手順(専門用語なし)
{
"store_name": { "ja": "...", "en": "...", "zh": "...", "ko": "..." },
"categories": [
{ "id": "drink", "name": { "ja": "ドリンク", "en": "Drinks", "zh": "饮品", "ko": "음료" } }
],
"items": [
{
"id": "item-001",
"published": true,
"category": "drink",
"name": { "ja": "カフェラテ", "en": "Café Latte", "zh": "拿铁咖啡", "ko": "카페라떼" },
"description": { "ja": "...", "en": "", "zh": "", "ko": "" },
"price": 550,
"image": "images/item-001-a1b2c3d4.webp"
}
]
}
published: false の商品はmenu.htmlに表示されない(削除ではなく非表示)name/descriptionの各言語が空欄の場合、menu.html側で自動的にjaへフォールバック表示する(翻訳API等は使わない)imageは相対パス。存在しない/読み込み失敗時はmenu.html側で🍽アイコンにフォールバックするcategoryを増やす場合はcategories配列に追加するだけでよいfetch("products.json?t=" + Date.now(), { cache: "no-store" }) で毎回最新を取得し、GitHub Pages/ブラウザキャッシュによって古いメニューが残り続けないようにしているpublished !== false の商品のみ描画textContent で挿入し、innerHTMLへユーザー入力(商品データ)を直接流し込まない(XSS対策)GitHub Contents API (/repos/{owner}/{repo}/contents/{path}) のみを使用する。
GET(sha付きで取得)PUT(直前に取得したshaを添えて更新。保存の直前に必ずproducts.jsonを再取得し、最新shaで上書きすることで、他端末の変更を検知する)PUT で images/{id}-{ランダム8桁hex}.webp へ保存admin.htmlはコード内にリポジトリ名を一切ハードコードしていない。window.location.hostname({owner}.github.io)とwindow.location.pathnameの先頭セグメントから自動的に owner/repo を判定する。これにより、このリポジトリをテンプレートとして複製するだけで、店舗ごとに設定を書き換える必要がない。
localStorageにPersonal Access Tokenを保存する方式を採用した。 判断理由:
sessionStorageではタブを閉じるたびに再設定が必要になり、実運用に耐えないconsole.logにも出力しないGitHub Contents APIには複数ファイルにまたがる完全なトランザクションが存在しない。そのため「画像は保存できたがproducts.jsonの更新に失敗した」という状態が起こり得る。この設計では:
見た目・操作感はadmin.htmlとほぼ同一だが、GitHub APIには一切アクセスせず、localStorage(キー: demo_menu_products_v1)だけを読み書きする。menu-demo.htmlも同じキーを参照するため、デモ管理画面での変更がその場でデモ公開画面に反映される体験を、実データに触れずに提供できる。「デモをリセット」で初期状態に戻せる。
textContentで挿入。innerHTMLは固定の骨組みマークアップにのみ使用し、ユーザー入力を直接連結しないサーバーサイドのビルドは不要。ローカルでは python -m http.server 等の静的サーバーで直接確認できる。admin.htmlの実データ確認にはGitHubのFine-grained PATが必要(テストにはPlaywright等でGitHub APIをモックする方法を推奨)。