The request or the response
The PDF is truncated or ends mid-document
The file opens and the last page is partial, or the viewer reports a damaged file it can partly recover. The size is smaller than it should be.
What is actually happening
The response was cut off. Either a stream was not fully consumed before the connection closed, or a proxy timed out mid-transfer, or the writing side finished before the reading side did.
Confirming it is this and not something that looks like it
Compare the bytes you wrote against the Content-Length header. Fewer bytes means truncation rather than corruption, which are different problems with different fixes.
The fix
Await the completion of the stream rather than the start of it. This is the bug that a fire-and-forget pipe produces: the handler returns, the framework closes the response, and the copy is still in progress.
import { pipeline } from "node:stream/promises";
import { Readable } from "node:stream";
// pipeline resolves when the copy is finished and rejects if either side fails.
// upstream.body.pipe(res) returns immediately and tells you nothing.
await pipeline(Readable.fromWeb(upstream.body), res);If that was not it
These produce the same symptom often enough to be worth ruling out before assuming the fix above did not work.
- A proxy or load balancer timeout shorter than the transfer takes
- A file handle closed before the write flushed
- Errors on the source stream that are never listened for
- A serverless function returning before its asynchronous work completes
Frequently asked
Does this happen with every rendering engine?
The behaviour behind it is not specific to one tool. The response was cut off. Anything rendering HTML to a paged medium has to make the same decision, so the fix travels with you if you change how the render happens.
Will this show up as an error in my logs?
Yes, this one surfaces as a thrown error or a failed status, which is why it is at least findable. The harder half is that the message often names the call that was in flight rather than the thing that actually failed.
Related failures
Problems people arrive at from the same starting point, or mistake for this one.
The downloaded PDF is corrupted and will not open
The bytes were treated as text somewhere.
The PDF opens in the browser instead of downloading
Content-Disposition is absent or set to inline.
413 Payload Too Large when sending HTML to render
The request body exceeded a limit.
401 Unauthorized from the render API
The key that arrived is not the key you think you sent.
429 Too Many Requests when generating PDFs in bulk
Unbounded concurrency.
CSS for paged documents, and what actually works
Which parts of the paged media specification a browser-based render implements.
Everything that goes wrong, by category
The full list, grouped by where in the pipeline it breaks.
Paste your markup and see the rendered document, without signing up.