Membangun fitur real-time dengan WebSocket dan SSE: memahami kapan memakai masing-masing, mengimplementasikan Server-Sent Events di Next.js, dan menghubungkan WebSocket untuk kolaborasi dua arah.

Tidak semua data harus dimuat ulang dengan refresh. Chat, notifikasi, status transaksi, dan daftar tugas yang berubah bersamaan antar pengguna menuntut real-time: server mendorong data ke browser begitu tersedia. Episode ini membahas dua teknologi utamanya — WebSocket dan SSE (Server-Sent Events) — dan kapan memakai masing-masing.
Real-time adalah masalah fullstack sejati: melibatkan transport di jaringan, state di server, dan sinkronisasi di client. Jika kalian sudah memahami episode 8 (state & data fetching), episode ini tinggal mengganti sumber data statis menjadi aliran (stream) hidup.
Kedua teknologi sama-sama membuat koneksi persisten dari browser ke server. Bedanya di arah aliran data:
| Aspek | WebSocket | SSE (Server-Sent Events) |
|---|---|---|
| Arah | Dua arah (client ↔ server) | Satu arah (server → client) |
| Protokol | ws:// (di atas TCP) | HTTP biasa, streaming |
| Reconnect | Manual, di tangan kalian | Otomatis bawaan browser |
| Balik proxy/LB | Butuh konfigurasi khusus | Ikut HTTP biasa (mudah) |
| Cocok untuk | Chat, game, kolaborasi | Notifikasi, feed, progres |
Tip
Aturan praktis 2026: gunakan SSE jika server cukup mendorong data (notifikasi, progres, feed) — ia jauh lebih sederhana, reconnect otomatis, dan berjalan mulus di balik proxy. Simpan WebSocket untuk kebutuhan dua arah yang intens: chat, kolaborasi kursor, game real-time. Memilih SSE saat cukup bisa menghemat banyak biaya operasional.
SSE di Next.js adalah route handler yang mengembalikan streaming response. Contoh nyata: notifikasi "catatan baru dibuat" untuk semua pengguna.
export async function GET() {
const stream = new ReadableStream({
start(controller) {
const send = (data: unknown) => {
controller.enqueue(`data: ${JSON.stringify(data)}\n\n`)
}
clients.add(send)
controller.enqueue(`data: ${JSON.stringify({ type: "connected" })}\n\n`)
},
cancel() {
clients.delete(send)
},
})
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
},
})
}Format SSE sangat sederhana: tiap pesan diawali data: dan diakhiri dua baris baru. Saat ada catatan baru, broadcast ke semua client:
// di dalam server action / setelah query
for (const send of clients) {
send({ type: "note.created", noteId, title })
}Di client, browser menangani koneksi dan reconnect otomatis — hanya butuh EventSource:
"use client"
import { useEffect } from "react"
export function useEvents(onEvent: (data: any) => void) {
useEffect(() => {
const source = new EventSource("/api/events")
source.onmessage = (e) => onEvent(JSON.parse(e.data))
return () => source.close()
}, [onEvent])
}Satu koneksi SSE memberi semua notifikasi tanpa polling. Perhatikan: clients adalah set di dalam memori proses server — cocok untuk satu instance; untuk multi-instance kita butuh pub/sub Redis (dibahas di episode 22).
Ketika butuh pengguna mengirim dan menerima secara real-time (chat, kolaborasi), WebSocket adalah jawabannya. Next.js mendukung WebSocket via route handler dengan adapter khusus; untuk aplikasi terpisah, Socket.IO tetap menjadi pilihan paling praktis karena menangani reconnect, fallback, dan room:
import { Server } from "socket.io"
export function setupSocket(httpServer) {
const io = new Server(httpServer, {
cors: { origin: process.env.APP_URL },
})
io.on("connection", (socket) => {
socket.on("chat:send", ({ room, message }) => {
io.to(room).emit("chat:receive", { message })
})
socket.join(`user:${socket.handshake.auth.userId}`)
})
}Client-nya simetris — membuka koneksi, mendengarkan event, dan mengirim event:
"use client"
import { io } from "socket.io-client"
export function useChat(room: string) {
const socket = io(process.env.NEXT_PUBLIC_SOCKET_URL, {
auth: { token: "session-token" },
})
return {
send: (message: string) => socket.emit("chat:send", { room, message }),
onReceive: (cb: (m: string) => void) =>
socket.on("chat:receive", ({ message }) => cb(message)),
}
}Autentikasi WebSocket wajib diperhatikan: token diverifikasi saat handshake (di auth), dan setiap event yang sensitif tetap diverifikasi di server — bukan hanya sekali di koneksi.
Jika kalian tidak ingin kelola koneksi sama sekali, Next.js 2026 punya jalan pintas: Server Actions + revalidation + optimistic updates (episode 7-8) untuk data satu pengguna, dan polling revalidate (episode 8) untuk data yang berubah tiap menit. Real-time sungguhan baru wajib ketika perubahan harus terlihat dalam detik oleh pengguna lain.
Selesaikan dengan urutan berikut:
src/app/api/events/route.ts (SSE) dan useEvents hook.createNote, broadcast note.created ke semua client./notes, tampilkan notifikasi toast saat event note.created masuk — tanpa refresh.curl -N http://localhost:3000/api/eventsTerminal harus menampilkan data: {"type":"connected"} dan tetap terbuka — bukti aliran berjalan.
Inti yang harus dibawa pulang:
Di episode 15 selanjutnya kita membahas caching & performance — Redis, CDN, ISR, dan Core Web Vitals — untuk mengoptimalkan aplikasi dari ujung ke ujung. Sampai jumpa di episode 15!