I am trying to implement SSE in NextJS 14 to update the user while processing some data.
Since the data is provided by the user, it needs to be a POST request, so I can’t use EventSource but have to use fetch().
I got the client side working like this:
"use client";
import { useState } from "react";
export default function Home() {
const [message, setMessage] = useState("");
async function onClick() {
const response = await fetch("/api/test2", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
const reader = response.body?.getReader();
if (!reader) return;
let decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (!value) continue;
const lines = decoder.decode(value);
const text = lines
.split("n")
.filter((line) => line.startsWith("data:"))[0]
.replace("data:", "")
.trim();
setMessage((prev) => prev + text);
}
}
return (
<div>
<button onClick={onClick}>START</button>
<p>{message}</p>
</div>
);
}
For the server side, by googling I found a code that works like this:
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest, res: NextResponse) {
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const text = "Some test text";
let index = 0;
const interval = setInterval(() => {
if (index < text.length) {
writer.write(`event: messagendata: ${text[index]}nn`);
index++;
} else {
writer.write(`event: messagendata: [DONE]nn`);
clearInterval(interval);
writer.close();
}
}, 1);
return new NextResponse(readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
The problem is that I need to send messages after some functions are done processing not in an interval.
Something like:
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest, res: NextResponse) {
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
writer.write(`event: "start"ndata:"Process 1"nn`)
processThatTakesTime();
writer.write(`event: "done"ndata:"Process 1"nn`)
writer.write(`event: "start"ndata:"Process 2"nn`)
anotherProcess();
writer.write(`event: "done"ndata:"Process 2"nn`)
writer.close();
return new NextResponse(readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
This code just sends all the messages in a single response, or even worse: closes the writer before even writing the messages to it.
I have tried adding “new Promise((resolve) => setTimeout(resolve, ms));” between them and all but nothing seemed to work.
- add a sleep function between the writes/close
- awaiting all the writes and close.
- turning them into a promise like
await new Promise<void>((resolve)=>{
setTimeout(()=>{
writer.write(`event: "start"ndata:"Process 1"nn`);
resolve();
),100}
});