Giới Thiệu
Trong bài viết này, tôi chia sẻ cách xây dựng real-time notifications sử dụng Server-Sent Events (SSE) với Upstash Redis, Next.js Server Actions và Vercel. Tận dụng message channels trong Upstash Redis có thể nâng cao đáng kể kiến trúc communication của ứng dụng, làm cho chúng responsive và dynamic hơn.
Demo
Công Nghệ Sử Dụng
- Next.js (Front-end và Back-end)
- Upstash Redis (Server-Sent Events với PUBLISH command)
- Tailwind CSS (Styling)
- Vercel (Deployment)
Yêu Cầu
- Node.js 18
- Tài khoản Upstash
- Tài khoản Vercel
Thiết Lập Upstash Redis
Đăng nhập Upstash, vào tab Redis tạo database.


Sau khi tạo xong, vào tab Details. Cuộn xuống phần Connect your database. Sao chép Redis URL lưu an toàn — dùng làm UPSTASH_REDIS_URL env variable.

Cuộn xuống phần REST API, chọn nút .env. Sao chép nội dung lưu an toàn — dùng variables UPSTASH_REDIS_REST_URL và UPSTASH_REDIS_REST_TOKEN.

Thiết Lập Dự Án
Clone repo và làm theo tutorial:
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
Tạo file .env thêm items từ trên:
# .env
# Upstash Redis Secrets
UPSTASH_REDIS_URL="rediss://default:...@...-...-...-....upstash.io:..."
UPSTASH_REDIS_REST_URL="https://...-...-...-....upstash.io"
UPSTASH_REDIS_REST_TOKEN="...="
Lưu ý: UPSTASH_REDIS_URL dùng "rediss" không phải "redis" — để dùng TLS/SSL.
Khởi chạy local:
npm run dev
Cấu Trúc Repository
Cấu trúc thư mục chính. File quan trọng (đánh dấu đỏ) thảo luận về:
- Hiểu Message Channels trong Upstash Redis
- Tạo Server-Sent Events API trong Next.js App Router
- Setup Next.js Server Actions với Upstash Redis publish notifications
- Setup Next.js frontend listen và display notifications real-time

Hiểu Message Channels Trong Upstash Redis
Trong Upstash Redis, publish/subscribe model là cốt lõi message channels. Publishers broadcast messages đến named channels, subscribers receive messages từ specific channels real-time. Model này enable seamless communication giữa các phần app.
Publish message đến channel dùng Edge-compatible library @upstash/redis:
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 Upstash Redis channel (ví dụ posts) dùng Node-compatible library ioredis:
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)
})
Khi publish message đến channel, all subscribers nhận ngay message — real-time communication hiệu quả trong Upstash Redis.
Tạo Server-Sent Events API Trong Next.js App Router
Server-Sent Events (SSE) gửi data real-time mới mà không cần multiple client requests. Khác với request-response truyền thống, SSE cho phép unidirectional data flow từ server đến client qua single, long-lived HTTP connection.
Implement SSE trong 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 Đến Upstash Redis Dùng Next.js Server Actions
Next.js Server Actions cho phép define server-side logic trực tiếp trong frontend code. Tiết kiệm việc tạo API Routes thủ công và track form submission status.
Với use server đầu function, đảm bảo function chỉ chạy server-side. Trong form submission Server Action, extract message từ form, dùng Vercel Header lấy country user, publish info as message đến 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 khi form submit bằng cách pass as handler vào form action event:
// File: app/page.jsx
import { publishNotification } from './actions'
const Home = () => {
return (
<>
<div>
<form action={publishNotification}>
{/* Client side form component */}
</form>
</div>
</>
)
}
export default Home
Setup Next.js Frontend Hiển Trạng Thái Pending Khi Form Submission Dùng React useFormStatus Hook
Code demo setup Next.js Form client component handle form submission, display pending state dùng React useFormStatus hook:
- Import
useFormStatushook từ React (mới release), cho status info của last form submission. - Dùng reactive variable
pendingchỉ form submission có đang tiến hành. - Nếu không pending,
resetform. - Dùng boolean
pendingshow conditional states.
'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 (
<>
<input placeholder="Your message" className="border rounded px-3 outline-none focus:border-black/50 py-2" type="text" name="message" required />
<button disabled={pending} className="hover:border-black/50 max-w-max border rounded py-1 px-3" type="submit">
{pending ? (
<div className="flex flex-row gap-x-2 items-center">
<div className="animate-spin border border-gray-800 rounded-full h-[15px] w-[15px]"></div>
<span>Publishing</span>
</div>
) : (
<>Publish Notification →</>
)}
</button>
</>
)
}
export default Form
Setup Next.js Frontend Lắng Nghe Persistent Server-Sent Events
Học cách setup minimal listener SSE API messages, và approach persist connection đến SSE API.
Lắng Nghe Server-Sent Events API Trong React Frontend
Dùng useEffect hook listen SSE API trong client component. Tạo EventSource instance trỏ đến endpoint /api/stream. Attach event listener cho event message, parse incoming data as JSON, process/display trong component. Cleanup function close SSE connection khi component unmount — tránh 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)
// Xử lý/hiển thị message
})
return () => { eventSource.close() }
}, [])
return <></>
}
export default ClientSideComponent
Persist Connection Đến Server-Sent Events API Trong React Frontend
Version enhanced: implement mechanism đảm bảo continuous, persistent connection SSE API bằng cách handle errors và auto-reconnect.
Thông qua function connectToStream chịu trách nhiệm establish và maintain connection SSE API.
- Initial Connection và 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 và Automatic Reconnection:
Event listener cho event error. Khi error (connection failure), close event source. Dùng setTimeout trigger reconnect sau delay minimal 1ms. Delay nhỏ tạo smoother, continuous reconnection không overwhelm server.
eventSource.addEventListener('error', () => {
eventSource.close()
setTimeout(connectToStream, 1)
})
- Handling SSE API Source Closure:
Event onclose detect khi SSE API source closed. Schedule reconnect attempt sau brief delay.
eventSource.onclose = () => {
setTimeout(connectToStream, 1)
}
Kết hợp các strategy này, function đảm bảo connection SSE API persistent. Dù error hay closure, React component keep attempting reconnect với minimal delay — maintain continuous connection hiệu quả.
Triển Khai Lên Vercel
Repo sẵn sàng deploy Vercel. Steps:
- Tạo
GitHub Repositoryvới app code - Tạo
New ProjectVercel Dashboard - Link
GitHub Repositorylàm project mới - Cập nhật
Environment Variablestừ.envlocal - Deploy! 🚀