Streaming long operations lets your app show results bit by bit instead of waiting for everything to finish. This makes users feel the app is faster and more responsive.
Streaming long operations in NextJS
export async function GET(request) { const stream = new ReadableStream({ async start(controller) { controller.enqueue(new TextEncoder().encode('Hello ')); await new Promise(resolve => setTimeout(resolve, 1000)); controller.enqueue(new TextEncoder().encode('World!')); controller.close(); } }); return new Response(stream); }
This example shows a Next.js API route using a ReadableStream to send data in chunks.
Use controller.enqueue() to send parts of the response, and controller.close() to finish.
export async function GET() { const stream = new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode('Part 1 ')); controller.enqueue(new TextEncoder().encode('Part 2')); controller.close(); } }); return new Response(stream); }
export async function GET() { const stream = new ReadableStream({ async start(controller) { controller.enqueue(new TextEncoder().encode('Loading')); await new Promise(r => setTimeout(r, 1000)); controller.enqueue(new TextEncoder().encode('...done')); controller.close(); } }); return new Response(stream); }
This Next.js API route streams text messages in three parts with 1-second pauses. The client receives updates progressively, improving user experience during long tasks.
import { NextResponse } from 'next/server'; export async function GET() { const stream = new ReadableStream({ async start(controller) { const encoder = new TextEncoder(); controller.enqueue(encoder.encode('Starting operation...\n')); await new Promise(r => setTimeout(r, 1000)); controller.enqueue(encoder.encode('Halfway there...\n')); await new Promise(r => setTimeout(r, 1000)); controller.enqueue(encoder.encode('Operation complete!\n')); controller.close(); } }); return new NextResponse(stream, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }); }
Streaming works best with clients that can handle partial responses, like browsers or fetch API.
Always set proper Content-Type headers to help clients interpret streamed data.
Use TextEncoder to convert strings to bytes before enqueueing in the stream.
Streaming sends data in chunks so users see progress during long operations.
Use ReadableStream and controller.enqueue() in Next.js API routes to stream responses.
Streaming improves user experience by reducing wait perception.
Practice
Solution
Step 1: Understand streaming purpose
Streaming sends data in chunks so users can see progress during long operations.Step 2: Identify user experience impact
This reduces the perceived wait time by showing partial results early.Final Answer:
Users see data progressively, reducing perceived wait time. -> Option AQuick Check:
Streaming improves user experience = A [OK]
- Thinking streaming caches data automatically
- Confusing streaming with data compression
- Assuming streaming prevents all errors
Solution
Step 1: Recall correct ReadableStream usage
ReadableStream requires a start method with a controller to enqueue data and close the stream.Step 2: Match syntax with Next.js streaming pattern
const stream = new ReadableStream({ start(controller) { controller.enqueue('data'); controller.close(); } }); return new Response(stream); uses controller.enqueue and controller.close correctly and returns a Response wrapping the stream.Final Answer:
const stream = new ReadableStream({ start(controller) { controller.enqueue('data'); controller.close(); } }); return new Response(stream); -> Option AQuick Check:
ReadableStream with controller.enqueue = B [OK]
- Using WritableStream instead of ReadableStream
- Calling non-existent methods like send() or push()
- Not closing the stream after enqueue
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
controller.enqueue(new TextEncoder().encode('Hello '));
await new Promise(r => setTimeout(r, 1000));
controller.enqueue(new TextEncoder().encode('World!'));
controller.close();
}
});
return new Response(stream);
}Solution
Step 1: Analyze streaming behavior
The stream first sends "Hello " immediately, then waits 1 second before sending "World!".Step 2: Understand client reception
The client receives data in two chunks with a delay, then the stream closes properly.Final Answer:
"Hello World!" received in two chunks with 1 second delay between. -> Option BQuick Check:
Streaming sends chunks with delay = C [OK]
- Assuming all data arrives after delay
- Thinking stream never closes
- Confusing async enqueue with syntax error
export async function GET() {
const stream = new ReadableStream({
start(controller) {
controller.enqueue('Data chunk');
// Missing controller.close()
}
});
return new Response(stream);
}Solution
Step 1: Check stream lifecycle
The code enqueues data but does not call controller.close(), so the stream stays open.Step 2: Understand client impact
The client waits forever for the stream to end, causing a hang or timeout.Final Answer:
The stream never closes, causing the client to wait indefinitely. -> Option CQuick Check:
Missing controller.close() = stream hangs [OK]
- Forgetting to close the stream
- Thinking enqueue must be async
- Misunderstanding Response usage
Solution
Step 1: Understand streaming large data
Streaming each item as JSON chunk progressively sends data without waiting for all items.Step 2: Identify correct streaming pattern
Using ReadableStream with async start allows looping over items, enqueueing each chunk, then closing the stream.Step 3: Evaluate other options
A requires multiple requests (inefficient); B sends all data at once (no streaming benefit); C uses wrong WritableStream type.Final Answer:
Use ReadableStream with async start(controller), loop over items, enqueue JSON strings, and close after all. -> Option DQuick Check:
Stream chunks in loop with ReadableStream = D [OK]
- Sending all data at once, losing streaming benefit
- Using WritableStream instead of ReadableStream
- Making multiple fetch calls instead of streaming
Ready to prove it?
Pass the test and this pattern moves unknown
