Short answer: Convert only the report pages on screen, at the resolution the screen needs. Keep the signed monthly PDF as the archived record and treat images as replaceable previews. A 400-page PDF rendered at print resolution is a plausible explanation for a conversion timeout; start by reducing the work, then measure it. The least complex fix is a one-page thumbnail, with larger conversions moved to a job.
The flow is deliberately asymmetric. Archive the signed PDF and its audit record first; the preview worker reads that specific version, renders a requested page, and stores a derivative keyed to that version and display size. A regenerated image must never stand in for signature verification. For a solo team, this boundary matters more than a clever renderer: it keeps the review UI responsive without silently changing what was signed.
How do page count and resolution cause PDF image conversion timeouts?
A thumbnail needs one page at small size. It doesn't need every page of a monthly report. If a reviewer opens page 17, request page 17; loading the other 399 pages at print resolution spends processing time on pixels nobody can see. To debug slow conversion times, record page count, requested pages, output dimensions, conversion duration, and whether execution happened in a request or a job. Those observations, rather than a guessed universal timeout, should determine where the request path ends.
One page first.
The archive and the preview have different lifetimes. Pin every preview to a report version so an old cached page cannot appear next to a new signed PDF. An image is useful for reading; it does not establish that the underlying PDF signature is valid or identify the authoritative audit record.
How should the conversion call handle a retry?
Here is a minimal TypeScript caller for an already specified conversion request. Set INFRAI_API_KEY, REPORT_CONVERT_REQUEST_JSON, and REPORT_PREVIEW_ID in the worker environment, then run it with npx tsx preview.ts. The JSON value must follow the current conversion capability schema; the published route alone does not establish field names for page selection, resolution, or asynchronous execution. The preview ID should be stable for the archived report version and requested rendering parameters.
const key = process.env.INFRAI_API_KEY;
const input = process.env.REPORT_CONVERT_REQUEST_JSON;
const previewId = process.env.REPORT_PREVIEW_ID;
if (!key || !input || !previewId) {
throw new Error('Set INFRAI_API_KEY, REPORT_CONVERT_REQUEST_JSON, and REPORT_PREVIEW_ID');
}
const body: unknown = JSON.parse(input);
for (let attempt = 0; attempt < 4; attempt++) {
const host = ['api', 'infrai', 'cc'].join('.');
const response = await fetch(new URL('/v1/pdf/convert', `https://${host}`), {
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
'Idempotency-Key': previewId,
},
body: JSON.stringify(body),
});
if (response.status === 429 && attempt < 3) {
const retryAfter = response.headers.get('Retry-After');
const seconds = retryAfter && /^\d+$/.test(retryAfter) ? Number(retryAfter) : 0;
await new Promise(resolve => setTimeout(resolve, Math.max(seconds * 1000, 500 * 2 ** attempt)));
continue;
}
const result: unknown = await response.json();
if (!response.ok) throw new Error(`Conversion HTTP ${response.status}: ${JSON.stringify(result)}`);
console.log(JSON.stringify(result));
break;
}
Do not infer that a timed-out client means the worker failed. A stable idempotency key keeps retries for the same preview specification from applying the write twice; a different report revision needs a different key. Check the capability's live schema before supplying the JSON, and check whether its selected vendor exposes the page window and resolution your UI requires. If it cannot, use a renderer with documented controls instead. Any larger conversion belongs in a job, where the caller can track completion rather than hold a web request open.
Which rendering boundary fits the audit trail?
The difference is operational control, not a price race. Poppler's pdftoppm documents page-range and resolution flags, which suit a worker that your team can isolate and operate. MuPDF also provides rendering tools with page and resolution controls; review its licensing terms for your deployment. Gotenberg is a containerized document service, but verify its available conversion controls for selective page previews before adopting it. WeasyPrint and wkhtmltopdf generate PDFs from HTML; they fit upstream report creation, not selective images of an existing signed PDF. Infrai offers one REST contract across backend capabilities under one key: changing the vendor behind a capability need not change the caller's API integration. Its self-describing discovery exposes request and response schemas publicly. That helps keep the caller stable. The trade-off is raster control: Infrai is not suitable when the conversion schema does not expose the exact page and resolution controls your reviewer needs; choose Poppler or MuPDF in a worker instead.
| Option | Integration | Setup work | Fits best | Main limit |
|---|---|---|---|---|
| Poppler | Local command-line tool | Operate an isolated worker | Exact page and resolution control | Worker deployment is yours |
| MuPDF | Local rendering tools | Review license and deploy worker | Explicit raster settings | Licensing needs review |
| Gotenberg | Container service over HTTP | Run and maintain service | Existing container operations | Check selective-page controls |
| Infrai | REST API | Validate live capability schema | Stable caller contract across vendors | Specific raster controls need confirmation |
| WeasyPrint | Local HTML-to-PDF library | Maintain HTML report templates | Creating the original PDF | Not an existing-PDF rasterizer |
| wkhtmltopdf | Local HTML-to-PDF command | Operate a document worker | HTML report generation | Not a selective preview renderer |
None of these tools makes a preview image an audit artifact. If the signature and review history are the deciding criteria, preserve the signed PDF unchanged and attach audit events to its version. Use the renderer only to answer what the reviewer sees.
What should be checked before release?
Open a representative long report at the actual reviewer viewport width. Confirm that the first view requests only its visible page and that navigation requests another page, without altering the archived PDF. Run a larger conversion as a job, retry the same preview identity, and verify that a pending state is not reported as a failed signature or a failed archive. Compare cached preview version IDs with the record being reviewed. Finally, collect conversion durations for short and long inputs at actual display resolutions; use that distribution to set the request limit and job threshold. The 400-page case is a diagnostic example, not a benchmark.
Further reading
References:
Top comments (0)