Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Real-Time Notifications Với Upstash Redis, Next.js Server Actions, Vercel: Hướng Dẫn Bước Đi Bước

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.

Real-Time Notifications Với Upstash Redis, Next.js Server Actions, Vercel: Hướng Dẫn Bước Đi Bước

Real-Time Notifications Với Upstash Redis, Next.js Server Actions, Vercel: Hướng Dẫn Bước Đi Bước

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.

Real-Time Notifications Với Upstash Redis, Next.js Server Actions, Vercel: Hướng Dẫn Bước Đi Bước

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.

Real-Time Notifications Với Upstash Redis, Next.js Server Actions, Vercel: Hướng Dẫn Bước Đi Bước

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

Real-Time Notifications Với Upstash Redis, Next.js Server Actions, Vercel: Hướng Dẫn Bước Đi Bước

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 useFormStatus hook từ React (mới release), cho status info của last form submission.
  • Dùng reactive variable pending chỉ form submission có đang tiến hành.
  • Nếu không pending, reset form.
  • Dùng boolean pending show 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 &rarr;</>
        )}
      </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.

  1. 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])
  })
  // ...
}
  1. 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)
})
  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 Repository với app code
  • Tạo New Project Vercel Dashboard
  • Link GitHub Repository làm project mới
  • Cập nhật Environment Variables từ .env local
  • Deploy! 🚀

Tham Khảo