app/sample/route.tsimport { NextRequest, NextResponse } from 'next/server';
export async function GET(_req: NextRequest) {
const stream = new TransformStream();
const writer = stream.writable.getWriter();
const encoder = new TextEncoder();
sendCharacters(text);
return new NextResponse(stream.readable, {
headers: {
'Content-Type': 'text/event-stream',
},
});
async function sendCharacters(remainingText: string) {
if (remainingText.length === 0) {
writer.close();
return;
}
const c = remainingText.at(0);
const rest = remainingText.slice(1);
const message = encoder.encode(`data: ${c}\n\n`);
await writer.write(message);
await sleep(100);
await sendCharacters(rest);
}
}
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const text =
'私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚る遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起こすごとに、すぐ「先生」といいたくなる。';
app/sample/SSE.ts'use client';
import { useState, useEffect } from 'react';
export const Component = () => {
const [message, setMessage] = useState('');
useEffect(() => {
const eventSource = new EventSource('sample');
const handleEventSourceMessage = (event: MessageEvent) => {
setMessage(p => `${p}${event.data}`);
};
const handleEventSourceError = (error: Event) => {
console.error('EventSource failed:', error);
eventSource.close();
};
eventSource.addEventListener('message', handleEventSourceMessage);
eventSource.addEventListener('error', handleEventSourceError);
return () => {
eventSource.removeEventListener('message', handleEventSourceMessage);
eventSource.removeEventListener('error', handleEventSourceError);
eventSource.close();
};
}, []);
return <div className="w-full p-8">{message}</div>;
};