The monthly report, generated while nobody waits

Reports are big, slow and bursty — the three things a synchronous request handles worst. Queue it, merge the sections, get a webhook when the file is on the CDN.

A rendered report PDF: a Q2 performance bar chart with one month highlighted, page 7 of 92.

The gateway gives up at 30s

A 90-page report with lazy-loaded charts takes longer than any HTTP proxy will keep the connection open.

Charts render empty

The screenshot lands before the animation finishes and you ship a report full of blank canvases.

Sections live on different pages

Cover, summary, appendix and terms are separate routes, so you end up gluing four PDFs together in a library that mangles fonts.

Everyone exports on the 1st

Bulk jobs starve the one report a customer is staring at, because your queue has no notion of who is waiting.

01

Merge the routes you already have. Pagination and the table of contents run across the whole document.

02

Wait for a signal your charts emit — an event or a data attribute — instead of guessing with a delay.

03

Go async and answer your user in 40ms with a job id. The webhook arrives when the file is ready.

04

Mark priority on interactive exports so the nightly batch never blocks them.

const job = await client.render({ merge: [ reportUrl(id, "cover"), reportUrl(id, "summary"), reportUrl(id, "detail"), reportUrl(id, "appendix") ], format: "A4", wait_for: "document.charts-ready", footer_html: "<div>{date} · {page} / {total}</div>", table_of_contents: true, priority: "interactive", async: true, callback_url: "https://acme.com/hooks/report", store: true }); // { id: "job_9fK2mQ", status: "queued" }

90 pages

merged from four routes, still one credit

40ms

to accept the job and answer your user

24h

of webhook retries if your endpoint is down