Supabase Realtime 実践:3つのモード比較とコラボアプリ開発
画面の「入力中…」という表示を見つめながら、17回目のチャットページ更新。相手は明らかにオンラインなのに、メッセージがどうしても出てこない。本当に「リアルタイム」なアプリを作るのは、想像よりずっと難しいものです。
WebSocket では数々の落とし穴を踏みました——接続が切れたら再接続、状態の同期、ブロードキャストメッセージの設計。そんな中、昨年 Supabase Realtime を使って、こうした面倒事を他人に任せられると気づいたのです。Supabase は3つのリアルタイムモードを提供しています:データベースの変更を監視する Postgres Changes、ユーザー状態を追跡する Presence、一時的なメッセージをブロードキャストする Broadcast。この3つにはそれぞれ適した場面があり、正しく使えば効率倍増、間違えると自分で穴を掘ることになります。
この記事では、この3つのモードを丁寧に分解して解説します——いつどれを使うか、コードはどう書くか、RLS セキュリティポリシーはどう設定するか。最後にはこれらを組み合わせて、完全なコラボチャットアプリを作ります。
Supabase Realtime の3大機能を比較
まず結論から:3つのモードはそれぞれ役割が違うので、混ぜて使ってはいけません。
Postgres Changes はデータベースの変更を監視します。チャットメッセージ、通知、注文ステータスのような、永続化が必要なデータに向いています。データはデータベースに保存され、クライアントは変更を購読するだけです。
Presence はユーザーのオンライン状態を追跡します。「誰がオンラインか」の表示、入力中インジケーター、共同編集中のカーソル位置などに向いています。データはデータベースに保存されずメモリに保持され、ユーザーが切断すると消えます。
Broadcast は一時的なメッセージをブロードキャストします。キャンバス上のカーソル移動、ゲーム内のリアルタイム位置、一時的な操作の同期などに向いています。Presence との違いは、Broadcast が高頻度の送信に向き、Presence が状態の同期に向くという点です。
1つの表で整理します:
| モード | データの保存先 | 典型的な場面 | 永続化 |
|---|---|---|---|
| Postgres Changes | PostgreSQL | チャットメッセージ、通知、注文ステータス | あり |
| Presence | メモリ(Realtime サービス) | オンラインユーザー、入力中インジケーター | なし |
| Broadcast | 保存しない(即時転送) | カーソル移動、リアルタイム位置 | なし |
「いっそ全部 Postgres Changes でいいのでは?」と思うかもしれません。正直、私も最初はそう考えました。でもコラボホワイトボードアプリを作ったとき、カーソルの移動ごとにデータベースへ書き込んだら、データベースの CPU が一気に 90% まで跳ね上がりました。その瞬間に分かったのです:永続化が不要なデータもあるのだと。
どのモードを選ぶかは、このシンプルな原則を覚えておきましょう:履歴を調べる必要があるなら Postgres Changes、現在の状態だけ気にするなら Presence、高頻度で一時的なら Broadcast。
Postgres Changes:データベースの変更を監視
このセクションでは、最も一般的な場面であるデータベース変更の監視を解説します。たとえばチャットルームに新着メッセージが来た、注文ステータスが変わった、誰かが「いいね」した——これらはすべて永続化が必要なデータです。
Supabase Realtime は PostgreSQL の logical replication(論理レプリケーション)の仕組みで変更監視を実現します。簡単に言うと、データベースで INSERT、UPDATE、DELETE 操作があるたびに、Realtime サービスがそれをキャプチャし、購読中のクライアントへ配信します。
Realtime 監視を有効化する
まずデータベース側で publication を有効にします。Supabase SQL Editor で次を実行します:
-- Realtime publication を有効化
ALTER publication supabase_realtime ADD TABLE messages;
-- UPDATE と DELETE を監視したいテーブルには REPLICA IDENTITY FULL を設定する必要がある
ALTER TABLE messages REPLICA IDENTITY FULL;
なぜ REPLICA IDENTITY FULL を設定するのでしょうか?デフォルトでは、PostgreSQL は変更行の主キーしか記録しないからです。変更前後の完全なデータを取得したい場合(たとえば監査ログを作る場合)は、このオプションを有効にする必要があります。注意:これはデータベースの書き込み量を増やすので、本当に必要なテーブルだけに有効化してください。
クライアントのサブスクリプションコード
次はクライアントのコードです。チャットメッセージを例にします:
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface Message {
id: string
content: string
user_id: string
created_at: string
}
export function useRealtimeMessages(roomId: string) {
const [messages, setMessages] = useState<Message[]>([])
useEffect(() => {
// まず履歴メッセージを取得
const fetchMessages = async () => {
const { data } = await supabase
.from('messages')
.select('*')
.eq('room_id', roomId)
.order('created_at', { ascending: true })
if (data) setMessages(data)
}
fetchMessages()
// 新着メッセージを購読
const channel = supabase
.channel(`messages:${roomId}`)
.on(
'postgres_changes',
{
event: 'INSERT', // 新規追加のみ監視
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}` // 特定の部屋でフィルタ
},
(payload) => {
// payload.new に新しく挿入されたデータが入っている
setMessages(prev => [...prev, payload.new as Message])
}
)
.subscribe()
// サブスクリプションのクリーンアップ
return () => {
supabase.removeChannel(channel)
}
}, [roomId])
return messages
}
いくつか注目すべきポイントがあります:
- 先に履歴、後に増分を購読:チャットルームに入ったらまず履歴メッセージを表示し、それから新着メッセージを受信します。これはよくある落とし穴で、購読だけして履歴を取得しないのは NG です。
- filter パラメータ:データベースのフィールドでフィルタし、無関係なメッセージを受け取らないようにします。構文は
フィールド名=eq.値です。 - サブスクリプションのクリーンアップ:コンポーネントのアンマウント時に
removeChannelを忘れずに。さもないとメモリリークします。
RLS セキュリティ設定(重要!)
ここは多くの人が見落としますが、本番アプリの要です。デフォルトでは、Realtime はテーブルの RLS(Row Level Security)ポリシーに従います。RLS を設定していないと、クライアントは何も受信できなかったり、受信すべきでないデータを受け取ってしまったりします。
たとえば、こんなチャットルーム用テーブルがあるとします:
-- メッセージテーブル
CREATE TABLE messages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
room_id uuid REFERENCES rooms(id),
user_id uuid REFERENCES auth.users(id),
content text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- RLS を有効化
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
-- 自分が所属する部屋のメッセージのみ閲覧を許可
CREATE POLICY "Users can view messages in their rooms"
ON messages FOR SELECT
USING (
room_id IN (
SELECT room_id FROM room_members
WHERE user_id = auth.uid()
)
);
-- 部屋メンバーのメッセージ送信を許可
CREATE POLICY "Room members can insert messages"
ON messages FOR INSERT
WITH CHECK (
room_id IN (
SELECT room_id FROM room_members
WHERE user_id = auth.uid()
)
);
Realtime サブスクリプションはこれらのポリシーを自動的に適用します。ユーザーは閲覧権限のあるメッセージ変更しか受け取れません。この点はとても重要です——フロントエンドでフィルタしようなどと考えてはいけません。それは安全ではありません。
サブスクリプションでデータを受信できないと気づいたら、まず2つを確認します:
- テーブルが
supabase_realtimepublication に追加されているか - RLS ポリシーが正しく設定されているか
Presence:ユーザーのオンライン状態を追跡
Presence は「今この瞬間に誰がオンラインか」という場面に向いています——チャットルームのオンライン人数表示、共同ドキュメントで誰がどこを編集しているか、誰が入力中か。これらのデータはデータベースに保存する必要はなく、メモリに保持すれば十分です。
基本原理
Presence の動作はこうです:各クライアントはチャンネルに参加した後、track() メソッドを呼んで自分の状態を登録します。Realtime サービスは全クライアントの状態スナップショットを保持し、誰かが参加・離脱・状態更新すると、すべての購読者に通知が届きます。
オンラインユーザー一覧を実装する
コードを見てみましょう。これはチャットルームのオンラインユーザーを表示するコンポーネントです:
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface UserPresence {
user_id: string
username: string
online_at: string
}
export function useOnlineUsers(roomId: string, currentUser: { id: string; username: string }) {
const [users, setUsers] = useState<UserPresence[]>([])
useEffect(() => {
const channel = supabase.channel(`room:${roomId}`, {
config: {
presence: {
key: currentUser.id // ユーザー ID を key に使う
}
}
})
channel
.on('presence', { event: 'sync' }, () => {
// sync 時に全オンラインユーザーを取得
const state = channel.presenceState()
// presenceState() は { [key]: [UserPresence, ...] } を返す
const onlineUsers = Object.values(state).flat() as UserPresence[]
setUsers(onlineUsers)
})
.on('presence', { event: 'join' }, ({ newPresences }) => {
// 新しいユーザーが参加
console.log('ユーザー参加:', newPresences)
})
.on('presence', { event: 'leave' }, ({ leftPresences }) => {
// ユーザーが離脱
console.log('ユーザー離脱:', leftPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// 購読成功後、自分の状態を登録
await channel.track({
user_id: currentUser.id,
username: currentUser.username,
online_at: new Date().toISOString()
})
}
})
return () => {
supabase.removeChannel(channel)
}
}, [roomId, currentUser])
return users
}
使い方はとても簡単です:
function ChatRoom({ roomId, currentUser }) {
const onlineUsers = useOnlineUsers(roomId, currentUser)
return (
<div className="flex items-center gap-2 mb-4">
<span className="text-sm text-gray-500">
{onlineUsers.length} 人がオンライン
</span>
<div className="flex -space-x-2">
{onlineUsers.map(user => (
<div
key={user.user_id}
className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm"
title={user.username}
>
{user.username[0]}
</div>
))}
</div>
</div>
)
}
入力中インジケーター
Presence では「入力中」表示も作れます。考え方はこうです:ユーザーが入力を始めたら自分の状態を更新し、しばらく入力が止まったら状態をクリアします。
export function useTypingIndicator(roomId: string, currentUser: { id: string }) {
const [typingUsers, setTypingUsers] = useState<string[]>([])
const channelRef = useRef<RealtimeChannel | null>(null)
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
const channel = supabase.channel(`room:${roomId}`)
channelRef.current = channel
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
const typing = Object.values(state)
.flat()
.filter((u: any) => u.is_typing && u.user_id !== currentUser.id)
.map((u: any) => u.username)
setTypingUsers(typing)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: currentUser.id,
is_typing: false
})
}
})
return () => {
supabase.removeChannel(channel)
}
}, [roomId, currentUser])
// ユーザーが入力を始めたときに呼ぶ
const setTyping = (isTyping: boolean) => {
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current)
}
channelRef.current?.track({
user_id: currentUser.id,
is_typing
})
// 3秒後に自動で入力状態をクリア
if (isTyping) {
typingTimeoutRef.current = setTimeout(() => {
channelRef.current?.track({
user_id: currentUser.id,
is_typing: false
})
}, 3000)
}
}
return { typingUsers, setTyping }
}
ここに小さな落とし穴があります:キー入力のたびに track() を呼んではいけません。頻度が高すぎると問題が起きます。デバウンスを入れるか、上記のように入力完了の数秒後に自動で状態をクリアしましょう。
Broadcast:カーソル追跡と即時メッセージ
Broadcast は3つのモードの中で最も「軽い」ものです——メッセージは保存も永続化もされず、送ったら忘れる。高頻度かつ一時的なデータ転送に向いています。
カーソル追跡の例
コラボホワイトボードやマルチユーザーエディターのようなアプリでは、各ユーザーのカーソル位置をリアルタイム表示する必要があります。この場面には Broadcast が最適です——カーソル位置はデータベースに保存する必要も、「過去のカーソルがどこにあったか」を知る必要もありません。
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState, useRef } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface CursorPosition {
user_id: string
username: string
x: number
y: number
color: string
}
export function useCursors(canvasId: string, currentUser: { id: string; username: string }) {
const [cursors, setCursors] = useState<Record<string, CursorPosition>>({})
const channelRef = useRef<RealtimeChannel | null>(null)
useEffect(() => {
const channel = supabase.channel(`canvas:${canvasId}`)
channelRef.current = channel
channel
.on('broadcast', { event: 'cursor' }, ({ payload }) => {
// 他のユーザーのカーソル位置を受信
if (payload.user_id !== currentUser.id) {
setCursors(prev => ({
...prev,
[payload.user_id]: payload
}))
}
})
.subscribe()
return () => {
supabase.removeChannel(channel)
}
}, [canvasId, currentUser])
// 自分のカーソル位置を送信
const sendCursor = (x: number, y: number) => {
channelRef.current?.send({
type: 'broadcast',
event: 'cursor',
payload: {
user_id: currentUser.id,
username: currentUser.username,
x,
y,
color: getUserColor(currentUser.id)
}
})
}
return { cursors, sendCursor }
}
// ユーザー ID から色を生成
function getUserColor(userId: string): string {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
const index = userId.charCodeAt(0) % colors.length
return colors[index]
}
コンポーネントで使う:
function CollaborativeCanvas({ canvasId, currentUser }) {
const { cursors, sendCursor } = useCursors(canvasId, currentUser)
const handleMouseMove = (e: React.MouseEvent) => {
sendCursor(e.clientX, e.clientY)
}
return (
<div className="relative w-full h-full" onMouseMove={handleMouseMove}>
{/* 他のユーザーのカーソルを表示 */}
{Object.entries(cursors).map(([userId, cursor]) => (
<div
key={userId}
className="absolute pointer-events-none"
style={{ left: cursor.x, top: cursor.y }}
>
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: cursor.color }} />
<span className="text-xs ml-1">{cursor.username}</span>
</div>
))}
</div>
)
}
Broadcast vs Presence
お気づきかもしれませんが、Broadcast と Presence は重なる部分があります。違いはこうです:
- Broadcast は高頻度の送信(毎秒数十回も)に使う。たとえばカーソル移動、ゲーム内の位置同期
- Presence は状態の同期(たまの更新)に使う。たとえば「誰がオンラインか」「入力中か」
両者を組み合わせると最も効果的です。Broadcast は「動く」データに、Presence は「状態」データに向いています。
総合実践:コラボチャットアプリを構築する
ここで3つのモードを組み合わせて、本格的なコラボチャットアプリを作ります。機能は次のとおりです:
- リアルタイムメッセージ(Postgres Changes)
- オンラインユーザー一覧(Presence)
- 入力中インジケーター(Presence)
データベースの準備
まずテーブルを作ります:
-- 部屋テーブル
CREATE TABLE rooms (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- 部屋メンバーテーブル
CREATE TABLE room_members (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
room_id uuid REFERENCES rooms(id) ON DELETE CASCADE,
user_id uuid REFERENCES auth.users(id),
joined_at timestamptz DEFAULT now(),
UNIQUE(room_id, user_id)
);
-- メッセージテーブル
CREATE TABLE messages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
room_id uuid REFERENCES rooms(id) ON DELETE CASCADE,
user_id uuid REFERENCES auth.users(id),
content text NOT NULL,
created_at timestamptz DEFAULT now()
);
-- Realtime を有効化
ALTER publication supabase_realtime ADD TABLE messages;
-- RLS を有効化
ALTER TABLE messages ENABLE ROW LEVEL Security;
-- RLS ポリシー:自分が所属する部屋のメッセージのみ閲覧
CREATE POLICY "Users can view messages in their rooms"
ON messages FOR SELECT
USING (
room_id IN (
SELECT room_id FROM room_members WHERE user_id = auth.uid()
)
);
-- RLS ポリシー:部屋メンバーのみメッセージ送信可
CREATE POLICY "Room members can send messages"
ON messages FOR INSERT
WITH CHECK (
room_id IN (
SELECT room_id FROM room_members WHERE user_id = auth.uid()
)
);
完全な React コンポーネント
これは簡易版のコラボチャットコンポーネントで、3つのモードをすべて使っています:
import { createClient } from '@supabase/supabase-js'
import { useEffect, useState, useRef, useCallback } from 'react'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
interface Message {
id: string
content: string
user_id: string
username: string
created_at: string
}
interface UserPresence {
user_id: string
username: string
is_typing?: boolean
}
export function CollaborativeChat({ roomId, currentUser }) {
const [messages, setMessages] = useState<Message[]>([])
const [onlineUsers, setOnlineUsers] = useState<UserPresence[]>([])
const [typingUsers, setTypingUsers] = useState<string[]>([])
const [inputValue, setInputValue] = useState('')
const channelRef = useRef<RealtimeChannel | null>(null)
const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
// 1つの channel を作り、3つの機能で共有
const channel = supabase.channel(`room:${roomId}`, {
config: { presence: { key: currentUser.id } }
})
channelRef.current = channel
// 1. Postgres Changes:新着メッセージを監視
channel.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}`
},
(payload) => setMessages(prev => [...prev, payload.new as Message])
)
// 2. Presence:オンラインユーザーと入力状態を監視
channel.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
const users = Object.values(state).flat() as UserPresence[]
setOnlineUsers(users)
const typing = users
.filter(u => u.is_typing && u.user_id !== currentUser.id)
.map(u => u.username)
setTypingUsers(typing)
})
// 購読して Presence を登録
channel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: currentUser.id,
username: currentUser.username,
is_typing: false
})
// 履歴メッセージを読み込み
const { data } = await supabase
.from('messages')
.select('*')
.eq('room_id', roomId)
.order('created_at', { ascending: true })
if (data) setMessages(data)
}
})
return () => supabase.removeChannel(channel)
}, [roomId, currentUser])
const sendMessage = async () => {
if (!inputValue.trim()) return
await supabase.from('messages').insert({
room_id: roomId,
user_id: currentUser.id,
content: inputValue.trim()
})
setInputValue('')
setTyping(false)
}
const setTyping = useCallback((isTyping: boolean) => {
if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current)
channelRef.current?.track({
user_id: currentUser.id,
username: currentUser.username,
is_typing
})
if (isTyping) {
typingTimeoutRef.current = setTimeout(() => setTyping(false), 3000)
}
}, [currentUser])
return (
<div className="flex h-full">
{/* 左側:オンラインユーザー */}
<div className="w-64 border-r p-4">
<h3 className="font-bold mb-2">オンライン ({onlineUsers.length})</h3>
{onlineUsers.map(user => (
<div key={user.user_id} className="py-1">
{user.username}
{user.is_typing && <span className="text-sm text-gray-500"> (入力中)</span>}
</div>
))}
</div>
{/* 右側:チャット */}
<div className="flex-1 flex flex-col">
<div className="flex-1 overflow-y-auto p-4">
{messages.map(msg => (
<div key={msg.id} className="mb-2">
<span className="font-bold">{msg.username}: </span>
{msg.content}
</div>
))}
{typingUsers.length > 0 && (
<div className="text-gray-500 text-sm">
{typingUsers.join(', ')} が入力中...
</div>
)}
</div>
<div className="p-4 border-t">
<input
value={inputValue}
onChange={e => { setInputValue(e.target.value); setTyping(true) }}
onKeyDown={e => e.key === 'Enter' && sendMessage()}
placeholder="メッセージを入力..."
className="w-full p-2 border rounded"
/>
</div>
</div>
</div>
)
}
設計上の重要ポイント
- 1つの Channel を共有:3つの機能で同じ channel を使い、接続数を減らします。
- Presence のデータ構造:
is_typingを同じ presence オブジェクトに入れます。 - クリーンアップ作業:コンポーネントのアンマウント時には必ず
removeChannelを。
パフォーマンスとセキュリティのベストプラクティス
ここまででコードはすべて動くようになりました。しかし本番投入前に、まだ処理すべき細部があります。
接続管理
各 channel は1つの WebSocket 接続を占有します。Supabase は複数チャンネルで1つの接続を共有できますが、乱用すればやはり問題が起きます。
推奨する方法:
- 1ページにつき最大2〜3個の channel
- 関連する機能は1つの channel を共有(上記のチャット例のように)
- ページを離れたらすぐ
removeChannel
// クリーンアップの例
useEffect(() => {
const channel = supabase.channel('my-channel')
channel.subscribe()
return () => {
// unsubscribe だけでなく、removeChannel を使う
supabase.removeChannel(channel)
}
}, [])
RLS は必須
「フロントエンドでフィルタすれば十分」などと考えてはいけません。Realtime サブスクリプションは RLS ポリシーを自動的に適用し、ユーザーは閲覧権限のあるデータ変更しか受け取れません。
サブスクリプションでデータを受信できないと気づいたら、確認の順番はこうです:
- テーブルが
supabase_realtimepublication に追加されているか - RLS ポリシーが正しいか(Supabase Dashboard の RLS テストツールを使う)
- ユーザーがログイン済みか(
auth.uid()が何を返すか)
料金の注意点
Supabase Realtime の課金は同時接続数で計算され、ピーク接続1000件あたり $10 です。たいていの中小規模アプリなら無料枠で足ります。ただし大量のユーザーが同時にオンラインになるアプリなら、channel 数のコントロールに注意してください。
公式にデモプロジェクト Multiplayer.dev があるので、3つのモードが実際に動く様子を見てみるとよいでしょう。
結論
ここまで色々書きましたが、どのモードを選ぶかは実はとてもシンプルです:
| 履歴を保存する? | 高頻度送信? | 推奨モード |
|---|---|---|
| はい | いいえ | Postgres Changes |
| いいえ | いいえ | Presence |
| いいえ | はい | Broadcast |
コラボ系アプリ(チャットルーム、ホワイトボード、ドキュメント編集)を作っているなら、3つのモードはおそらくどれも使うことになります。Postgres Changes でメッセージを保存し、Presence でオンライン状態を追跡し、Broadcast でカーソルを処理します。
まずは Multiplayer.dev を触って、実際の動作を体験してみることをおすすめします。それから小さなチャット demo を自分で書いてみましょう——これが最速の習得方法です。
次回は Supabase Storage、ファイルアップロードと画像処理について書く予定です。Realtime について質問があれば、ぜひコメントで議論しましょう。
FAQ
Supabase Realtime の3つのモードはどう違いますか?
• Postgres Changes はデータベースの変更を監視し、データを永続化。チャットメッセージや注文ステータスに最適
• Presence はユーザーのオンライン状態を追跡し、データはメモリに保持。オンライン一覧や入力中インジケーターに最適
• Broadcast は一時的なメッセージをブロードキャストし保存しない。カーソル追跡やリアルタイム位置に最適
なぜサブスクリプションでデータを受信できないのですか?
1. テーブルが supabase_realtime publication に追加されているか(ALTER publication supabase_realtime ADD TABLE テーブル名 を実行)
2. RLS ポリシーが正しく設定されているか(Realtime サブスクリプションは RLS を自動適用)
3. ユーザーにデータの閲覧権限があるか(auth.uid() の返り値を確認)
Postgres Changes と Broadcast のどちらを選ぶべきですか?
Presence のデータは永続化されますか?
Realtime サブスクリプションは RLS にどう影響しますか?
1ページでいくつの channel を使えますか?
4分で読めます · 公開日: 2026年4月15日 · 更新日: 2026年6月8日
Supabase 実践ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
Supabase Storage 実践:ファイルアップロード・権限制御・CDN 高速化
Supabase Storage の使い方を、ファイルアップロードから権限設定、CDN 連携まで一通り解説。RLS ポリシー、ユーザー分離、Smart CDN、画像変換まで網羅します
第 4 / 10 記事
次の記事
Supabase Realtime 実践:WebSocket 接続管理と切断再接続戦略
Supabase Realtime の実践テクニックを詳解。WebSocket 接続管理、切断再接続戦略、Postgres Changes によるリアルタイム購読を網羅。Broadcast・Presence・Postgres Changes の3機能の選定と本番環境のベストプラクティスを習得
第 6 / 10 記事
関連記事
Supabase 入門:PostgreSQL + Auth + Storage のオールインワンバックエンド
Supabase 入門:PostgreSQL + Auth + Storage のオールインワンバックエンド
Supabase データベース設計:テーブル構造・リレーション・Row Level Security 完全ガイド
Supabase データベース設計:テーブル構造・リレーション・Row Level Security 完全ガイド
Supabase Auth 実践:メール認証・OAuth・セッション管理
コメント
GitHubアカウントでログインしてコメントできます