ในโพสต์นี้ ผมเล่าเกี่ยวกับการสร้าง real-time notifications ใช้ Server-Sent Events (SSE) ด้วย Upstash Redis, Next.js Server Actions และ Vercel การใช้ message channels ใน Upstash Redis สามารถเพิ่มประสิทธิภาพ communication architecture ของแอปพลิเคชันได้อย่างมาก ทำให้ responsive และ dynamic มากขึ้น
Demo
สิ่งที่เราจะใช้
- Next.js (Front-end และ Back-end)
- Upstash Redis (Server-Sent Events ด้วยคำสั่ง PUBLISH)
- Tailwind CSS (Styling)
- Vercel (Deployment)
สิ่งที่คุณต้องการ
- Node.js 18
- บัญชี Upstash
- บัญชี Vercel
ตั้งค่า Upstash Redis
สร้างบัญชี Upstash login ไปที่แท็บ Redis สร้าง database


สร้างเสร็จ ไปแท็บ Details เลื่อนลงหา Connect your database คัดลอก Redis URL เก็บไว้เป็น UPSTASH_REDIS_URL

เลื่อนลงหา REST API กด .env คัดลอกเก็บไว้เป็น UPSTASH_REDIS_REST_URL และ UPSTASH_REDIS_REST_TOKEN

ตั้งค่าโปรเจกต์
โคลน repo ติดตามคู่มือ:
git clone https://github.com/rishi-raj-jain/upstash-nextjs-publish-messages-with-sse-example
cd upstash-nextjs-publish-messages-with-sse-example
npm install
สร้าง .env ใส่ค่าจากข้างต้น:
# .env
# Upstash Redis Secrets
UPSTASH_REDIS_URL="rediss://default:...@...-...-...-....upstash.io:..."
UPSTASH_REDIS_REST_URL="https://...-...-...-....upstash.io"
UPSTASH_REDIS_REST_TOKEN="...="
สังเกต UPSTASH_REDIS_URL ใช้ "rediss" ไม่ใช่ "redis" เพื่อ TLS/SSL
รัน local:
npm run dev
โครงสร้าง Repository
โครงสร้างโฟลเดอร์หลัก (ไฟล์สีแดงจะอธิบายต่อ):
- ทำความเข้าใจ Message Channels ใน Upstash Redis
- สร้าง Server-Sent Events API ใน Next.js App Router
- Setup Next.js Server Actions กับ Upstash Redis publish notifications
- Setup Next.js frontend listen และ display notifications real-time

ทำความเข้าใจ Message Channels ใน Upstash Redis
ใน Upstash Redis publish/subscribe model เป็นหัวใจของ message channels Publishers broadcast messages ไป named channels subscribers รับ messages จาก channels เฉพาะแบบ real-time Model นี้ enable seamless communication ระหว่างส่วนต่างๆ ของแอปพลิเคชัน
Publish message ไป channel ด้วย @upstash/redis (Edge compatible):
import { Redis } from '@upstash/redis'
const redis = Redis.fromEnv()
await redis.publish('posts', JSON.stringify({
date: new Date().toString(),
message: "I am a new message."
}))
Subscribe channel posts ด้วย ioredis (Node compatible):
import Redis from 'ioredis'
const redisSubscriber = new Redis(process.env.UPSTASH_REDIS_URL)
const setKey = 'posts'
redisSubscriber.subscribe(setKey, (err) => { if (err) console.log(err) })
redisSubscriber.on('message', (channel, message) => {
if (channel === setKey) console.log(message)
})
Publish message ไป channel → subscribers ทุกตัวรับ message ทันที real-time communication ใน Upstash Redis
สร้าง Server-Sent Events API ใน Next.js App Router
Server-Sent Events ส่ง data ใหม่ real-time โดยไม่ต้อง multiple client requests ต่างจาก request-response แบบเดิม SSE enable unidirectional flow จาก server ไป client ผ่าน single long-lived HTTP connection
Implement SSE ใน Next.js App Router:
// File: app/api/stream/route.js
export const dynamic = 'force-dynamic'
export async function GET() {
const encoder = new TextEncoder()
const customReadable = new ReadableStream({
start(controller) {
const message = 'Hey, I am a message.'
controller.enqueue(encoder.encode(`data: ${message}\n\n`))
},
})
return new Response(customReadable, {
headers: {
'Content-Type': 'text/event-stream; charset=utf-8',
Connection: 'keep-alive',
'Cache-Control': 'no-cache, no-transform',
'Content-Encoding': 'none'
},
})
}
Publish Messages ไป Upstash Redis ด้วย Next.js Server Actions
Next.js Server Actions กำหนด server-side logic ใน frontend code ได้โดยตรง ประหยัดการสร้าง API Routes manual และ track form submission status
ใช้ use server ทำให้ function รันฝั่ง server ใน form submission Server Action ดึง message จาก form ใช้ Vercel Header ดึง country ของ user publish ไป channel posts ใน Upstash Redis
// File: app/actions.jsx
'use server'
import { Redis } from '@upstash/redis'
import { headers } from 'next/headers'
export async function publishNotification(formData) {
'use server'
const redis = Redis.fromEnv()
const message = formData.get('message')
const headersList = headers()
const country = headersList.get('x-vercel-ip-country')
await redis.publish(
'posts',
JSON.stringify({ message, country, date: new Date().toString() })
)
}
Invoke Server Action ตอน submit form:
// File: app/page.jsx
import { publishNotification } from './actions'
const Home = () => {
return (
<>
>
)
}
export default Home
Setup Next.js Frontend แสดง Pending State ระหว่าง Form Submission ด้วย useFormStatus hook
Code ต่อไปสาธิต Next.js Form client component จัดการ form submission แสดง pending state ด้วย React useFormStatus hook:
- Import
useFormStatushook จาก React ให้ status info ของ form submission ล่าสุด - ใช้
pendingreactive variable บอกว่า form submission กำลังดำเนินการ - ถ้าไม่ pending
resetform - ใช้
pendingboolean แสดง conditional states ของ form
'use client'
import { useEffect } from 'react'
import { useFormStatus } from 'react-dom'
const Form = () => {
const { pending } = useFormStatus()
useEffect(() => {
if (!pending) document.getElementById('publish-form').reset()
}, [pending])
return (
<>
>
)
}
export default Form
Setup Next.js Frontend ฟัง Server-Sent Events ตลอดเวลา
เรียนรู้ setup minimal listener สำหรับ SSE API messages และ approach persist connection ไป SSE API
ฟัง Server-Sent Events API ใน React Frontend
ใช้ useEffect hook สร้าง EventSource instance ชี้ไป /api/stream attach event listener สำหรับ message event parse data เป็น JSON process/display ใน component
Cleanup function ปิด SSE connection ตอน unmount ป้องกัน memory leaks
import { useEffect, useState } from 'react'
const ClientSideComponent = () => {
useEffect(() => {
const eventSource = new EventSource('/api/stream')
eventSource.addEventListener('message', (event) => {
const tmp = JSON.parse(event.data)
// Do something with the obtained message
})
return () => { eventSource.close() }
}, [])
return <>>
}
export default ClientSideComponent
Persist Connection ไป Server-Sent Events API ใน React Frontend
Enhanced version: mechanism ensure persistent connection SSE API โดย handle errors และ auto-reconnect
ผ่าน function connectToStream จัดการ establish และ maintain connection:
- Initial Connection และ Message Handling:
const connectToStream = () => {
const eventSource = new EventSource('/api/stream')
eventSource.addEventListener('message', (event) => {
const tmp = JSON.parse(event.data)
setPosts(prevPosts => [...prevPosts, tmp])
})
// ...
}
- Error Handling และ Automatic Reconnection:
Event listener สำหรับ error event ถ้า error (เช่น connection failure) ปิด event source setTimeout เรียก reconnect หลัง delay 1ms ช่วย smooth reconnection
eventSource.addEventListener('error', () => {
eventSource.close()
setTimeout(connectToStream, 1)
})
- จัดการ SSE API Source Closure:
ใช้ onclose detect ปิด source schedule reconnect หลัง brief delay
eventSource.onclose = () => {
setTimeout(connectToStream, 1)
}
รวมกลยุทธ์เหล่านี้ connection SSE API persistent แม้ errors หรือ closures component reconnect ด้วย minimal delay
ปรับใช้งานบน Vercel
- สร้าง
GitHub Repositoryด้วย app code - สร้าง
New Projectใน Vercel Dashboard - Link
GitHub Repositoryเป็น project ใหม่ - Scroll ลง อัปเดต
Environment Variablesจาก.envlocal - Deploy! 🚀