One fetch call. Zero Chromium in your deployment. Pay per page.
Render PDFs from any Node.js runtime — Express, Next.js API routes, NestJS, Fastify, Koa, AWS Lambda, Vercel Functions, Cloudflare Workers — with a single HTTP request. Skip the 200 MB Puppeteer download, the ARM/x86 binary mismatches, and the memory spikes that kill your serverless function.
const API_KEY = process.env.HTML2DOCHUB_API_KEY!;
export async function renderPdf(html: string): Promise<Buffer> {
const res = await fetch("https://api.html2dochub.com/v1/render", {
method: "POST",
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
type: "pdf",
html,
options: { format: "A4", margin: "18mm" },
}),
});
if (!res.ok) throw new Error(`Render failed: ${res.status}`);
const job = await res.json();
const pdfRes = await fetch(job.download_url);
return Buffer.from(await pdfRes.arrayBuffer());
}// app/api/invoice/[id]/pdf/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(
_req: NextRequest,
{ params }: { params: { id: string } },
) {
const invoice = await getInvoice(params.id);
const html = renderInvoiceTemplate(invoice);
const res = await fetch("https://api.html2dochub.com/v1/render", {
method: "POST",
headers: {
"X-API-Key": process.env.HTML2DOCHUB_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ type: "pdf", html, options: { format: "A4" } }),
});
const job = await res.json();
const pdf = await fetch(job.download_url).then((r) => r.arrayBuffer());
return new NextResponse(pdf, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="invoice-${params.id}.pdf"`,
},
});
}const express = require("express");
const app = express();
app.get("/invoice/:id.pdf", async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
const html = renderTemplate("invoice", { invoice });
const apiRes = await fetch("https://api.html2dochub.com/v1/render", {
method: "POST",
headers: {
"X-API-Key": process.env.HTML2DOCHUB_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ type: "pdf", html, options: { format: "A4" } }),
});
const job = await apiRes.json();
const pdfRes = await fetch(job.download_url);
res.set("Content-Type", "application/pdf");
res.set("Content-Disposition", `attachment; filename="invoice-${req.params.id}.pdf"`);
pdfRes.body.pipe(res);
});
app.listen(3000);import { Worker, Queue } from "bullmq";
export const reportQueue = new Queue("reports");
new Worker("reports", async (job) => {
const { reportId, userId } = job.data;
const idempotencyKey = `report:${reportId}:v${job.attemptsMade}`;
const res = await fetch("https://api.html2dochub.com/v1/render", {
method: "POST",
headers: {
"X-API-Key": process.env.HTML2DOCHUB_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "pdf",
mode: "async",
html: await buildReportHtml(reportId),
webhook_url: `${process.env.BASE_URL}/webhooks/pdf-ready`,
tag: `user:${userId}`,
idempotency_key: idempotencyKey,
}),
});
if (!res.ok) throw new Error(`Render enqueue failed: ${res.status}`);
const { id: html2DochubJobId } = await res.json();
await db.report.update({ where: { id: reportId }, data: { html2DochubJobId } });
}, { concurrency: 4 });Pay only for pages rendered. No subscriptions. No minimum monthly fee.
Free account. No credit card required. API ready in minutes.
Get your free API key