1. 伺服器發送事件(SSE)
// app/api/events/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
const interval = setInterval(() => {
const data = JSON.stringify({
time: new Date().toISOString(),
message: 'Server update',
});
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
}, 3000);
// Cleanup khi client disconnect
return () => clearInterval(interval);
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
});
}
// Client component
'use client';
import { useEffect, useState } from 'react';
export function LiveUpdates() {
const [messages, setMessages] = useState<string[]>([]);
useEffect(() => {
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
setMessages(prev => [...prev, data.message]);
};
eventSource.onerror = () => {
eventSource.close();
};
return () => eventSource.close();
}, []);
return (
<ul>
{messages.map((msg, i) => (
<li key={i}>{msg}</li>
))}
</ul>
);
}
2.WebSockets 與 Socket.io
// server.ts — Custom server cho WebSocket
import { createServer } from 'http';
import { Server } from 'socket.io';
import next from 'next';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const httpServer = createServer((req, res) => handle(req, res));
const io = new Server(httpServer, {
cors: { origin: '*' },
});
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
socket.on('join-room', (room: string) => {
socket.join(room);
});
socket.on('send-message', (data: { room: string; message: string }) => {
io.to(data.room).emit('new-message', {
id: Date.now(),
text: data.message,
sender: socket.id,
timestamp: new Date().toISOString(),
});
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
httpServer.listen(3000);
});
3. 聊天元件
'use client';
import { useEffect, useState, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
type Message = {
id: number;
text: string;
sender: string;
timestamp: string;
};
export function Chat({ room }: { room: string }) {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
const socket = io();
socketRef.current = socket;
socket.emit('join-room', room);
socket.on('new-message', (message: Message) => {
setMessages(prev => [...prev, message]);
});
return () => { socket.disconnect(); };
}, [room]);
const sendMessage = () => {
if (!input.trim()) return;
socketRef.current?.emit('send-message', { room, message: input });
setInput('');
};
return (
<div>
<div className="h-96 overflow-y-auto">
{messages.map(msg => (
<div key={msg.id}>
<strong>{msg.sender}:</strong> {msg.text}
</div>
))}
</div>
<div className="flex gap-2">
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && sendMessage()}
placeholder="Nhập tin nhắn..."
/>
<button onClick={sendMessage}>Gửi</button>
</div>
</div>
);
}
4. 推播器集成
// lib/pusher.ts
import PusherServer from 'pusher';
import PusherClient from 'pusher-js';
export const pusherServer = new PusherServer({
appId: process.env.PUSHER_APP_ID!,
key: process.env.NEXT_PUBLIC_PUSHER_KEY!,
secret: process.env.PUSHER_SECRET!,
cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER!,
useTLS: true,
});
export const pusherClient = new PusherClient(
process.env.NEXT_PUBLIC_PUSHER_KEY!,
{ cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER! }
);
// Server Action — trigger event
'use server';
import { pusherServer } from '@/lib/pusher';
export async function sendNotification(userId: string, message: string) {
await pusherServer.trigger(`user-${userId}`, 'notification', {
message, timestamp: new Date().toISOString(),
});
}
// Client — listen for events
'use client';
import { useEffect } from 'react';
import { pusherClient } from '@/lib/pusher';
export function Notifications({ userId }: { userId: string }) {
useEffect(() => {
const channel = pusherClient.subscribe(`user-${userId}`);
channel.bind('notification', (data: { message: string }) => {
// Show toast notification
alert(data.message);
});
return () => { pusherClient.unsubscribe(`user-${userId}`); };
}, [userId]);
return null;
}
下一篇: 電子郵件、付款和第三方 — 重新發送、Stripe、OAuth。