Search code examples
javascriptreactjsnext.jsserver-sent-events

EventSource gets all chunks at once when streaming via Nextjs API Routes


Problem

I am trying to stream data to my React Frontend via EventSources, which kind of works. The problem is that I receive all chunks of data at once instead of time after time. Which kind of defeats the purpose of the method as it is basically now a GET request.

Code

I am using Next.js api routes and this is my code on this route:

res.writeHead(200, {
  "Content-Type": "text/event-stream",
  "Cache-Control": "no-cache",
  Connection: "keep-alive",
});

answerStream.data
  .on("data", (chunk: string) => res.write(chunk))
  .on("error", (error: Error) => {
    console.error(error);
    res.end();
  })
  .on("end", () => res.end());

This is the code in the Frontend:

  const eventSource = new EventSource(url);
  eventSource.addEventListener("message", e => {
    try {
      if (e.data == "[DONE]") eventSource.close();
      else {
        const messageObject = JSON.parse(e.data);
        setArticle(state => (state += messageObject?.choices[0]?.text));
      }
    } catch (error) {
      console.log(error);
    }
  });

  eventSource.addEventListener("close", e => {
    console.log("Connection closed with the server");
    setSubmitting(false);
  });

  eventSource.addEventListener("error", e => {
    setError(e?.message || "Leider ist ein Fehler aufgetreten");
    setSubmitting(false);
    eventSource.close();
  });

Info

Is there anything in my implementation wrong? Or does Next.js have a strange handling of EventSource?


Solution

  • Solution

    Ok, I have found the solution in this Github discussion:

    It seems the issue is that the middleware adds a gzip encoding which the browser has negotiated using the header

    In order to fix this, it is needed to overwrite that behavior by adding 'Content-Encoding': 'none' to the headers on the server:

    res.writeHead(200, {
        Connection: 'keep-alive',
        'Content-Encoding': 'none',
        'Cache-Control': 'no-cache',
        'Content-Type': 'text/event-stream',
      });
    

    Alternatively, one could use a custom server.js.