NextJS 14 SSE with TransformStream() sending messages in a single response

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.

  1. add a sleep function between the writes/close
  2. awaiting all the writes and close.
  3. turning them into a promise like
await new Promise<void>((resolve)=>{
    setTimeout(()=>{
        writer.write(`event: "start"ndata:"Process 1"nn`);
        resolve();
    ),100}
});