render_timeout
HTTP 504 · code: "render_timeout" · type: https://docs.formfeed.dev/errors/render-timeout
A step of the render took longer than its limit. There is no limit on the render as a whole; each step has its own:
| Step | Limit | detail |
|---|---|---|
| The template language (Jinja2, Handlebars) fills the template with the data | 5 seconds | Template render took 6120 ms; the limit is 5000 ms |
Chromium loads the document, until none of its requests is running (waitFor.networkIdle says how) | 30 seconds | Render exceeded 30000 ms |
Waiting until an element matching waitFor.selector is visible | waitFor.timeoutMs, 5 seconds by default, at most 30 | Render exceeded 30000 ms (the message names 30 seconds in any case) |
Taking the picture of an image output, or of the element of image.clipSelector | 30 seconds | Render exceeded 30000 ms |
| Printing the PDF | 120 seconds | Printing the PDF exceeded 120000 ms |
| Filling a Word or PowerPoint template | 10 seconds | Template render took … ms; the limit is 10000 ms |
Converting a Word or PowerPoint template, or a document sent to /pdf/convert, to PDF | 60 seconds for the whole render or conversion | The conversion did not finish in time |
| Making an e-invoice, or validating a PDF/UA-1 file | 20 seconds | The e-invoice conversion did not finish in time, The PDF/UA validation did not finish in time |
A document of several hundred pages prints in tens of seconds, well inside its limit. A waitFor.delayMs pause has no limit of its own and is capped at 10 seconds. A Liquid template that runs longer than 5 seconds answers template_runtime_error instead, and a url render whose page does not load in 30 seconds answers navigation_failed.
Handlebars partials nested deeper than 8 levels also answer this code (Partials nest deeper than 8), although no time ran out. Jinja2 and Liquid have no such depth limit.
The render is recorded as failed and costs nothing. How long a render may take lists the same limits from the request's side.
Example
{
"type": "https://docs.formfeed.dev/errors/render-timeout",
"title": "Render timed out",
"status": 504,
"detail": "Render exceeded 30000 ms",
"code": "render_timeout",
"location": null,
"render_id": "rnd_7da09ab6cd9946b798c187c2a9350852",
"request_id": "req_4b7c1e9a2f3d4c5b8a6e7f9012"
}
Common causes
- A template that loops over very large data, or nests loops deeply.
- Images, fonts or scripts from a slow server: the document counts as loaded only when none of its requests is running.
- A script that keeps the network busy, such as polling or an analytics beacon.
- A
waitFor.selectorthat never becomes visible, because the script that adds the element failed, the selector does not match, or the element stays hidden. - A Word or PowerPoint file with many large pictures or pages, which LibreOffice converts slowly.
How to fix it
- Reduce the work in the template: compute totals and groupings in your application and send the results.
- Host images and fonts on a fast CDN, or in the file library, and check that
waitFor.selectormatches an element your script really adds. - Set
waitFor.networkIdletofalsefor documents that keep a connection open. - For office documents, compress the pictures, or split the document.
Retry?
Only if the cause was passing, such as a slow server for images. A template that is too slow fails the same way again. An answer of 504 is not stored under the Idempotency-Key, so a retry with the same key really runs. Async renders are retried by the queue twice, and a render that times out every time ends as worker_exhausted; a timeout of a Word or PowerPoint conversion is not retried.