What Causes This Error?
This error occurs in Nuxt server routes (powered by H3/Nitro) when you try to send an invalid stream as a response. The stream must be a valid ReadableStream or Node.js stream.
The Problem
// server/api/stream.ts
export default defineEventHandler((event) => {
// ❌ Wrong - not a valid stream
return sendStream(event, { data: 'not a stream' })
// ❌ Wrong - null/undefined
return sendStream(event, null)
// ❌ Wrong - already consumed stream
const stream = getBodyStream()
await stream.read() // Consumed!
return sendStream(event, stream) // Error!
})
The Fix
Return Valid ReadableStream
// server/api/stream.ts
export default defineEventHandler((event) => {
// ✅ Correct - Web ReadableStream
const stream = new ReadableStream({
start(controller) {
controller.enqueue('Hello ')
controller.enqueue('World')
controller.close()
}
})
return sendStream(event, stream)
})
Return Node.js Stream
// server/api/file.ts
import { createReadStream } from 'fs'
export default defineEventHandler((event) => {
// ✅ Correct - Node.js readable stream
const stream = createReadStream('/path/to/file.txt')
return sendStream(event, stream)
})
Common Patterns
File Download
// server/api/download.ts
import { createReadStream, statSync } from 'fs'
import { join } from 'path'
export default defineEventHandler((event) => {
const filePath = join(process.cwd(), 'files', 'document.pdf')
// Set headers
setHeader(event, 'Content-Type', 'application/pdf')
setHeader(event, 'Content-Disposition', 'attachment; filename="document.pdf"')
// Get file size for Content-Length
const stat = statSync(filePath)
setHeader(event, 'Content-Length', stat.size)
// ✅ Stream the file
return sendStream(event, createReadStream(filePath))
})
Proxy Stream
// server/api/proxy.ts
export default defineEventHandler(async (event) => {
const response = await fetch('https://api.example.com/large-file')
if (!response.body) {
throw createError({ statusCode: 500, message: 'No response body' })
}
// ✅ Forward the stream
setHeader(event, 'Content-Type', response.headers.get('content-type') || '')
return sendStream(event, response.body)
})
Server-Sent Events (SSE)
// server/api/events.ts
export default defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'text/event-stream')
setHeader(event, 'Cache-Control', 'no-cache')
setHeader(event, 'Connection', 'keep-alive')
// ✅ Create SSE stream
const stream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder()
let count = 0
const interval = setInterval(() => {
count++
controller.enqueue(encoder.encode(`data: Event ${count}\n\n`))
if (count >= 10) {
clearInterval(interval)
controller.close()
}
}, 1000)
}
})
return sendStream(event, stream)
})
Transform Stream
// server/api/transform.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// ✅ Create transform stream
const stream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder()
const transformed = body.text.toUpperCase()
controller.enqueue(encoder.encode(transformed))
controller.close()
}
})
return sendStream(event, stream)
})
Error Handling
// server/api/safe-stream.ts
export default defineEventHandler(async (event) => {
try {
const response = await fetch('https://api.example.com/data')
if (!response.ok) {
throw createError({
statusCode: response.status,
message: 'Upstream error'
})
}
if (!response.body) {
throw createError({
statusCode: 500,
message: 'No stream available'
})
}
return sendStream(event, response.body)
} catch (error) {
// Handle stream errors
throw createError({
statusCode: 500,
message: 'Stream error: ' + error.message
})
}
})
Checking Stream Validity
// server/api/validate-stream.ts
export default defineEventHandler(async (event) => {
const stream = getStreamFromSomewhere()
// ✅ Validate before sending
if (!stream) {
throw createError({ statusCode: 500, message: 'No stream' })
}
if (typeof stream.getReader !== 'function' &&
typeof stream.pipe !== 'function') {
throw createError({ statusCode: 500, message: 'Invalid stream type' })
}
return sendStream(event, stream)
})
Alternative: sendWebResponse
// server/api/response.ts
export default defineEventHandler(async (event) => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('Hello'))
controller.close()
}
})
// ✅ Alternative using sendWebResponse
const response = new Response(stream, {
headers: { 'Content-Type': 'text/plain' }
})
return sendWebResponse(event, response)
})
Quick Checklist
- Stream is a valid ReadableStream or Node.js stream
- Stream hasn’t been consumed/closed
- Stream is not null or undefined
- Set appropriate Content-Type header
- Handle errors in stream creation
- Consider using sendWebResponse for complex responses