0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「みんなで同時に見る」ライブ注文ボードをつくってみた:誰が見ていて、誰が操作中かが分かる

0
Last updated at Posted at 2026-10-23

対象: 前の記事でRealtimeの三目並べを動かして、「これを仕事の画面にするなら、どうなるの?」が気になった方
所要時間: 40〜60分 / 使うもの: ブラウザ(タブを複数)、ターミナル、Supabaseのプロジェクト(Freeプランで可)
確認日: 2026年10月(画面や料金は変わることがあります)
前の記事(手順編): https://qiita.com/tomozilla/items/6656954cf2bbd52e7a57
ビジネス編(やさしい解説・note): https://note.com/tomozilla/n/n79d740dc996d
サンプルコード一式(GitHub): https://github.com/tomozilla/realtime-tictactoe-demo
記事の中のコードは日本語コメントです。リポジトリの本体は英語コメントで、日本語コメント版(この記事と同じもの)は ja/ フォルダーにあります。

前の記事の三目並べでは、途中から入った人の盤面が空っぽになって、「ああ、Broadcastは過去を覚えていないんだ」と学びました。そのときは、「状態を教えて」と放送する工夫で乗り切りました。

でも、仕事の画面を想像すると、もっとしっかり考えたくなりました。飲食店の厨房の注文ボード、サポート窓口のチケット管理、倉庫の出荷ボード。複数の人が同時に開いて、誰かが動かしたら、全員の画面がすぐ変わる。しかも、「いま誰が見ているのか」「誰がそのカードを触っているのか」が分かると、二重作業が減りそうです。

そこで、ライブ注文ボードをつくってみました。

0. 先に伝えておきたいこと(正直ベース)

  • デモ用の最小構成です。 ログインなしで、誰でも注文を読める・追加できる・更新できる設定にしています(後ろの「セキュリティ」を読んでください)。
  • 前の記事の三目並べとは、考え方を変えました。「いまの状態」はデータベースに置いて、Realtimeは「変わったよ」の通知と「誰がいるか」だけを運びます。
  • 費用:Freeプランは、Realtimeのメッセージ数が月200万、同時接続が200まで(ドキュメントの料金表より)。このボードは、3タブで試しただけなので、十分でした。数え方の細かい規則(1つの変更を、見ている人数ぶん数えるか、など)は、私は確かめていません。 もし「1つの変更が、見ている人の数だけメッセージとして数えられる」なら、10人が見ているボードで1日500回の変更があると、月に約15万メッセージです(これは、その仮定のもとでの計算です)。料金ページで確認してください。
  • 未検証:認証つきの非公開チャンネル、多人数(私は3タブまで)、スマホのブラウザ、別のブラウザ(Macのchromeだけで確認しました)、2人が同時に同じカードを動かしたときの扱い。

1. つくったもの:ライブ注文ボード

3つの列(受付 / 調理中 / 完了)に、注文のカードが並びます。ボタンで、次の列に動かします。

Aliceの画面。Bobが、いちばん上のカードを見ている

画面の右上に、いま見ている人(A、B)が出ます。Bobがカードを選ぶと、Aliceの画面のそのカードに、**「Bobが見ています」**という札が付きました。

Bobが「調理中へ →」を押すと、Aliceの画面で、自動でカードが動きます。

Bobが動かしたあとのAliceの画面。右下の「いま起きたこと」にも出る

「Bobさんが更新・5秒前」と、右下の「いま起きたこと」に、誰が何をしたかが出ます。

反映までの時間

BobのクリックからAliceの画面が変わるまでを、同じパソコンの2つのタブで、5回測りました。369〜745ミリ秒でした(データベースへの書き込みの時間を含みます。別の端末・別のネットワークでは未測定です)。

2. 前の記事との違い:「状態」はどこに置く?

全体像

前の記事(三目並べ) 今回(注文ボード)
「いまの状態」を持つのは 各タブの画面(持っているだけ) データベース
Realtimeの役割 手を伝える(Broadcast) 変更を知らせる(Postgres Changes)+ 誰がいるか(Presence)
途中から入った人 盤面が空 → 「状態を教えて」で解決 入室時にデータベースを読むだけ

途中から参加しても、全部見える

Carolが、あとから参加しました。

途中から入ったCarolの画面。全注文と、3人がいることと、Bobの「見ています」が見える

入った瞬間に、6件の注文が、いまの状態で並びました。 三目並べのときの「空っぽ」は、起きません。入室したときに、データベースから全部読んでいるからです。

3. つくり方

データベース

create table if not exists public.orders (
  id bigint primary key generated always as identity,
  customer text not null,
  item text not null,
  status text not null default '受付' check (status in ('受付', '調理中', '完了')),
  updated_by text,
  updated_at timestamptz not null default now()
);
alter table public.orders enable row level security;

-- ★ デモ用:ログインなし(anon)で、読む・追加する・更新するのを許可
create policy "demo: anyone can read orders"   on public.orders for select to anon, authenticated using (true);
create policy "demo: anyone can add orders"    on public.orders for insert to anon, authenticated with check (true);
create policy "demo: anyone can update orders" on public.orders for update to anon, authenticated using (true) with check (true);

-- 変更をRealtimeで配信する対象にする
alter publication supabase_realtime add table public.orders;

最後の1行が、Realtimeで「変更の通知」を受け取るための設定です。

画面

supabase-js で、1つのチャンネルに、変更の通知と**Presence(誰がいるか)**の両方をつなぎます。

channel = supabase.channel('orders-board', { config: { presence: { key: myKey } } })
  .on('postgres_changes', { event: '*', schema: 'public', table: 'orders' }, (p) => {
    orders.set(p.new.id, p.new); render()          // 変更が届いたら、手元の状態を更新
  })
  .on('presence', { event: 'sync' }, () => {
    people = Object.values(channel.presenceState()).flat()   // いまいる人の名簿
    render()
  })
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({ name: me, focus: null })   // 自分を名簿に載せる
      await loadAll()                                  // 入室時に、データベースから全件を読む
    }
  })

「誰がどのカードを見ているか」は、Presenceに、focus: 注文のID を載せるだけです。カードを選んだら channel.track({ name: me, focus: id }) を呼び、他の人の画面では、focus が一致するカードに「◯◯が見ています」を出します。

動かすには、前の記事と同じ要領です。

cd app
cp config.example.js config.js     # URL と publishable キーを入れる
python3 -m http.server 8765 --bind 127.0.0.1
# http://127.0.0.1:8765/index.html?name=Alice  /  ...?name=Bob  のように、タブごとに名前を変える

4. つまずいたこと

① 裏にしていたタブが、いつの間にか切れていた(一番大きなつまずき)

3つのタブを開いて試していたとき、気づきました。Carolの画面には、注文が5件、「いま見ている人」はAとCの2人。ところが、Bobの画面は、注文が4件のままでした。Aliceが追加した注文が、届いていません。

Bobの画面の左上を見ると、「切れました」。そして、そのまま戻りませんでした。画面には、何も警告が出ていませんでした。

つまり、接続が切れたことに、使っている本人が気づけない状態でした。しかも、画面はふだんどおりに見えるので、古い注文を見て仕事をしてしまうかもしれません。

原因は、私は特定できていません。別のタブを前面にして操作している間に、裏のタブが切れたようです(ブラウザは裏のタブのタイマーを間引くことがあるので、それが関係しているのでは、という私の推測です。確かめてはいません)。ノートパソコンのスリープや、Wi-Fiの切り替えでも、似たことが起きそうです。

直し方は、3つです。

  1. 切れたら、画面を灰色にして、「古い情報かもしれません」と出す
  2. 自動でつなぎ直す(3秒後。裏のタブから戻ってきたときは、すぐ)
  3. つながり直したら、データベースから全件を読み直す(切れている間の変更を取りこぼさないため)
.subscribe(async (status) => {
  if (mine !== channel) return                       // 古いチャンネルの通知は無視
  if (status === 'SUBSCRIBED') { setConn(true); await mine.track(...); await loadAll() }
  else if (['CLOSED', 'CHANNEL_ERROR', 'TIMED_OUT'].includes(status)) { setConn(false); scheduleReconnect() }
})
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && !connected) scheduleReconnect(200) })

切断を試すために、?debug=1 を付けたときだけ、window.__rt.disconnect() でわざと切れるようにしました。Bobの画面を切って、その間にAliceが注文を追加すると、こうなりました。

切れているとき(灰色になり、「古い情報かもしれません」と出る)

数秒後、Bobの画面は自動でつながり直して、切れている間にAliceが追加した注文も含めて、6件に追いつきました。「いま見ている人」にも、Bobが戻りました。

② 「つなぎ直している間」に、灰色が消えていた

最初の直し方では、つなぎ直しを始めた瞬間に、灰色が消えて「接続中…」になっていました。中身はまだ古いままなのに、普通の見た目です。「一度つながったあとで切れたなら、つながり直すまでは、古い情報として扱う」に直しました。

③ Presenceのキーに、ランダムな文字を足した

Presenceは「キー」ごとに状態を持ちます。キーを名前だけにすると、同じ人が2つのタブを開いたときに、片方の状態が上書きされそうだったので、最初から名前にランダムな文字を足しました(名前だけのキーで実際に困るかどうかは、試していません)。

④ 反映の確認は、実際に別のタブで見る

ボタンを押した本人の画面は、データベースの返事ですぐ変わります。他の人の画面が変わるかどうかは、別のタブで見ないと分かりません。今回は、Alice・Bob・Carolの3タブで確かめました。

5. セキュリティ(デモ用の構成です)

  • 誰でも、全注文を読めて、追加できて、更新できます。 「更新できる」には、どの列でも(金額や状態など)含まれます。本物のアプリでは、ログインした人だけにして、更新できる列や行を絞ってください(私は試していません)。
  • 変更の通知が、ポリシーの対象になるかどうか(読む権限がない行の変更は、届かないはずですが)は、私は確かめていません。
  • 「誰が更新したか」(updated_by)は、画面が送る名前です。なりすませます。 監査用の記録には使えません。

6. 実際の運用画面にするなら

どれも、私は試していません。

  • ログインと権限:店長だけが「完了」にできる、など
  • 同時に同じカードを動かしたとき:今は、あとから届いたほうが勝ちです。更新時刻で、古い画面からの更新を弾く方法が使えそうです
  • 音や通知:新しい注文が入ったら、音を鳴らす
  • 履歴:いつ誰が動かしたかを、別の表に残す
  • 多人数・長時間:画面を開きっぱなしにして、つなぎ直しがうまく働き続けるか

まとめ

  • 仕事の画面にするときは、「いまの状態」はデータベース、「変わったよ」と「だれがいる」はRealtime、と役割を分けると安心だった
  • 入室時にデータベースを読むので、途中から入っても、全部見える
  • Presenceに「どのカードを見ているか」を載せると、二重作業を防ぐ札を出せる
  • 反映までは、私の測定で約0.4〜0.7秒(同じパソコン、5回)
  • 接続は、いつの間にか切れる。 切れたことに気づける画面と、つなぎ直して読み直すしくみが要る
  • 誰でも更新できる設定は、デモ限定。本番では、ログインと更新の範囲の制限を

コードは、上のGitHubにあります。次は、画像変換を使った「お知らせCMS」です。


スクリーンショットは、記事用に作った無料プランのデモ環境で撮影したものです。名前(Alice・Bob・Carol)と注文は、すべて架空のものです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?