Rendral

Stack · Firebase

Generate PDFs in Firebase Functions

Running Puppeteer in Cloud Functions is a well-worn trap: you provision 1GB or more of memory just to keep Chrome alive, cold starts stretch into seconds, and each deploy ships a large bundle. Costs follow memory and duration, so both go the wrong way.

Render remotely instead and let the function do what it's good at — fetch the data, call the API, and stream the result into Cloud Storage for the client to download.

01Call it from Firebase

render.ts
// functions/index.js
import { onCall } from "firebase-functions/v2/https";
import { getStorage } from "firebase-admin/storage";

export const invoicePdf = onCall({ memory: "256MiB" }, async (request) => {
  const html = await buildInvoiceHtml(request.data.invoiceId);

  const res = await fetch("https://rendral.com/api/v1/pdf", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + process.env.RENDRAL_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      html,
      options: { format: "A4", pageNumbers: true },
    }),
  });

  const pdf = Buffer.from(await res.arrayBuffer());
  const file = getStorage().bucket().file("invoices/" + request.data.invoiceId + ".pdf");
  await file.save(pdf, { contentType: "application/pdf" });

  const [url] = await file.getSignedUrl({
    action: "read",
    expires: Date.now() + 3600_000,
  });
  return { url };
});

Frequently asked

How much memory does the function need?

256MiB is plenty, because your code only builds HTML and moves bytes. Puppeteer-based functions typically need 1GiB or more purely to host Chrome.

Should I return the PDF or a link?

A link. Callable functions have response size limits, and writing to Cloud Storage plus a signed URL scales better and lets the client retry the download.

Does this reduce cost?

Usually, yes. Cloud Functions bill on memory and duration, and Chrome inflates both. A small, fast function that awaits HTTP is a much cheaper shape.

Can I trigger it from Firestore?

Yes — use onDocumentCreated instead of onCall to generate a document whenever a record is written, then store the resulting URL back on the document.

Related

Start rendering in minutes.

A free tier, five endpoints, and SDKs for Node and Python.