Stream NDJSON Progress from a Route Handler
Hard · Route Handlers
A route handler can return a Response whose body is a ReadableStream. The client receives each chunk as soon as it is enqueued, instead of waiting for the whole answer: ideal for progress updates of a long job. A common format is NDJSON (newline-delimited JSON): one JSON object per line, each ending with \n.
Write progressResponse(tasks, log), the body of GET in app/api/jobs/route.ts. log is only there so the tests can see what the server did.
- Return new Response(stream, { headers }) with Content-Type: application/x-ndjson and Cache-Control: no-cache.
- Run the tasks one after another: await sleep(task.ms), then enqueue {"task":name,"status":"ok","done":i,"total":n} ("failed" when task.fail is true; a failed task does not stop the job), where i counts finished tasks. After enqueueing, push sent:<name> to the log.
- After the last task enqueue {"status":"complete","ok":<number ok>,"failed":<number failed>}, close the stream and push closed.
- Streams carry bytes: encode every line with a TextEncoder (enqueueing plain strings breaks the reader).
- Cancellation: when the client stops reading (a closed tab; here the driver calls reader.cancel("client disconnected")), the stream's cancel(reason) runs. Push cancelled:<reason> and stop: no later task may be sent, and closed never appears. Check a cancelled flag after every await, because a task can be in the middle of its sleep when the client leaves.
Tip: the pull(controller) method of an underlying source is called whenever the reader wants more data, so doing one task per pull is a natural fit.