第二章:Supabase Realtime 實戰:監聽資料庫變化並讓前端 UI 自動即時更新

Supabase 被譽為 Firebase 的最強開源替代品,而它最殺手級的功能之一,就是 Supabase Realtime

傳統開發中,如果你想做一個「只要有人新增資料,畫面上立刻多一筆紀錄」的功能,你需要自己架設 Socket.io 伺服器、處理連線斷線重連、還要擔心多個節點間的狀態同步。現在,Supabase 幫你把這一切都封裝成了幾行簡單的 JavaScript 程式碼。

步驟一:在 Supabase 後台開啟 Realtime 權限

基於效能與資安考量,Supabase 預設是關閉 Realtime 推播的。你必須明確告訴它「哪些資料表需要被廣播」。

  1. 登入 Supabase 專案儀表板。
  2. 前往左側選單的 Database > Replication
  3. 找到 Source,點擊 `0 tables` (或目前的數字) 以開啟設定。
  4. 找到你要監聽的表,例如我們建立一個用來儲存聊天訊息的表 `chat_messages`。
  5. 把它旁邊的開關打開 (Toggle ON)。

步驟二:前端建立監聽頻道 (Channel)

回到我們的 Next.js 前端專案。 假設你已經有了一個聊天室元件 `ChatRoom.tsx`,裡面有一個 `messages` 的 useState 來儲存目前畫面上的訊息。

我們要加上一段 `useEffect`,用來向 Supabase 註冊一條「專屬廣播頻道」。

```typescript "use client" import { useEffect, useState } from "react" import { createClient } from "@/utils/supabase/client"

export default function ChatRoom() { const [messages, setMessages] = useState<any[]>([]) const supabase = createClient()

useEffect(() => { // 1. 定義頻道的名稱 (可以自訂,例如 'public-chat') const channel = supabase.channel('public-chat')

// 2. 訂閱 Postgres 的變更事件 (INSERT)
channel.on(
  'postgres_changes',
  {
    event: 'INSERT',           // 我們只想監聽「新增」事件
    schema: 'public',          // 預設 schema
    table: 'chat_messages'     // 指定我們要監聽的資料表
  },
  (payload) => {
    // 3. 當有新資料塞進資料庫時,這個 Callback 就會被觸發!
    console.log("收到新訊息囉!", payload.new)
    
    // 把新收到的訊息,加入到 React 的 State 中,觸發畫面重新渲染
    // 注意:永遠使用函數式的 setMessages 寫法來避免閉包陷阱
    setMessages((prevMessages) => [...prevMessages, payload.new])
  }
)
// 4. 正式發起訂閱連線
.subscribe((status) => {
  console.log("連線狀態:", status);
})

// 5. Cleanup 函式:當使用者離開此頁面時,記得退訂頻道以節省資源
return () => {
  supabase.removeChannel(channel)
}

}, [])

// 渲染聊天畫面 return (

🌐 全球即時大廳
{messages.map((msg) => (
{msg.sender_name}
{msg.content}
))}
) } ```

步驟三:測試即時魔法

這段程式碼的魔法在哪裡? 你可以打開兩個不同的瀏覽器視窗(例如一個 Chrome,一個 Safari),同時進入這個聊天室頁面。

然後,你可以直接打開 Supabase 後台的 Table Editor,手動在 `chat_messages` 資料表裡面插入一筆新資料。 砰! 不到 0.1 秒的時間,你打開的兩個瀏覽器視窗上,會「同時且瞬間」浮現出你剛才輸入的新訊息,完全不需要按重新整理 (F5)!

這背後完全沒有寫任何的 `fetch` 輪詢,也沒有寫龐大的 Socket.io Node.js 伺服器,一切都發生在雲端與瀏覽器之間的 WebSocket 管線中。

深入解析:它是怎麼辦到的?

當你在前端執行 `.subscribe()` 時,Supabase JS SDK 默默地在背景與 Supabase 伺服器建立了一條 WebSocket 連線。

Supabase 伺服器內部有一個超強的 Elixir 開源模組,它會去監聽 PostgreSQL 資料庫底層的「WAL (Write-Ahead Log,寫入預先日誌)」。只要資料庫一發生變動,它就立刻把變動轉成 JSON 格式,順著 WebSocket 管線噴給所有正在訂閱這個 Table 的前端客戶端。

常見陷阱:為什麼我收不到推播?

如果你照著寫卻收不到更新,請檢查以下三個最常踩坑的地方:

  1. 忘記在後台開啟 Replication(佔 90% 的新手錯誤)。這就好像你買了收音機,但電台沒有發送訊號一樣。
  2. RLS (Row Level Security) 擋住了:如果你開啟了 RLS,但沒有寫對應的 Policy 允許匿名 (anon) 或登入者讀取,推播會被攔截。
  3. State 更新邏輯寫錯:在 `setMessages` 裡面,如果你寫的是 `setMessages([...messages, payload.new])`,會產生 Closure Trap(閉包陷阱),導致每次都只留下一筆資料。永遠記得使用函數式的寫法 `setMessages(prev => [...prev, new])`

學會這招,你已經具備了開發股票看盤、多人共編協作工具的基礎了!下一章,我們要把難度升級,結合 AI API 來實作炫酷的打字機串流特效。


🔍 Supabase Realtime 底層架構

訂閱類型

| 類型 | 用途 | |------|------| | * | 監聽所有 CRUD | | INSERT | 新資料新增 | | UPDATE | 資料更新 | | DELETE | 資料刪除 |

效能注意事項

// ❌ 不要訂閱整個 schema
supabase.channel('db').on('postgres_changes', 
  { event: '*', schema: 'public' }, callback)

// ✅ 只訂閱需要的表格
supabase.channel('messages').on('postgres_changes', 
  { event: 'INSERT', schema: 'public', table: 'messages' }, callback)

🔄 Realtime 的實際應用場景

場景一:協作編輯

當多個使用者同時編輯同一份文件時,Realtime 確保每個人的畫面都保持一致:

// 監聽 documents 表格的 UPDATE 事件
supabase.channel('document-{{id}}')
  .on('postgres_changes', {
    event: 'UPDATE',
    schema: 'public',
    table: 'documents',
    filter: `id=eq.{{id}}`
  }, (payload) => {
    // 即時更新編輯器內容
    editor.setContent(payload.new.content)
  })
  .subscribe()

場景二:即時通知

當後端處理完一個任務時,即時通知前端:

type Notification = {
  id: string
  user_id: string
  title: string
  message: string
  read: boolean
  created_at: string
}

supabase.channel('notifications')
  .on('postgres_changes', {
    event: 'INSERT',
    schema: 'public',
    table: 'notifications',
    filter: `user_id=eq.{{userId}}`
  }, (payload) => {
    // 顯示通知 Toast
    toast(payload.new.title, { description: payload.new.message })
    // 更新未讀計數
    unreadCount.value++
  })
  .subscribe()


WebSocket 伺服器實作

WebSocket 連線生命週期

客戶端                         伺服器
   │                             │
   │── HTTP Upgrade Request ──→  │
   │←── 101 Switching Protocols ─│
   │                             │
   │── WebSocket Frame ────────→  │
   │←── WebSocket Frame ────────│
   │                             │
   │── Close Frame ────────────→  │
   │←── Close Frame ────────────│

伺服器端實作

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f'User says: {data}')
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast('A user disconnected')

下一章預告:即時聊天 UI

WebSocket 伺服器建好之後,下一章用 React 建立即時聊天 UI。

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!