Client-Side PDF Generation in Serverless

When your serverless backend can't run PDF libraries (no headless Chrome, no native modules), generate the PDF in the browser instead. The server renders an HTML page with the data embedded, and client-side JavaScript builds and downloads the PDF. The user doesn't notice the difference.

Tags

Client-Side PDF Generation in Serverless

The Lesson

When your serverless backend can't run PDF libraries (no headless Chrome, no native modules), generate the PDF in the browser instead. The server renders an HTML page with the data embedded, and client-side JavaScript builds and downloads the PDF. The user doesn't notice the difference.

Context

MyReachBand generates printable bracelet sheets — 8 strips per 8.5"x11" page, each with a QR code, band text, and a URL. Cloudflare Workers can't run headless Chrome (paid add-on) or Node.js PDF libraries like pdfkit (native dependencies). The solution: the Worker serves an HTML page that loads jsPDF and qrcode-generator from CDNs, generates the PDF client-side, and triggers a download.

What Happened

  1. Explored server-side options: Cloudflare Browser Rendering (paid), pdf-lib (limited text/layout support), raw PDF byte generation (complex). None were practical for a free-tier project.
  2. Chose client-side generation with jsPDF (handles PDF creation, text, images) and qrcode-generator (creates QR as SVG, converted to PNG via canvas).
  3. The Worker serves a page at /bracelets/:id/print with the bracelet's share URL and band text embedded in the HTML.
  4. JavaScript on the page generates the QR code, builds the PDF with precise dimensions (inches, not pixels), and triggers doc.save() which downloads the file.
  5. Iterated on layout: dotted cut lines (jsPDF's setLineDashPattern renders as blocks — had to draw dots manually), vertical text positioning, and font sizing.
  6. Used doc.getTextWidth() to measure text for centering, and discovered that jsPDF's angle parameter for rotated text rotates around the text origin, requiring manual offset calculation.

Key Insights

Examples

Loading libraries from CDN

<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.2/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>

Generating a QR code as PNG data URL

var qr = qrcode(0, 'H');
qr.addData('https://myreachband.com/b/ABC123?t=token');
qr.make();
var svg = qr.createSvgTag({cellSize: 8, margin: 0});
var blob = new Blob([svg], {type: 'image/svg+xml'});
var img = new Image();
img.onload = function() {
  var canvas = document.createElement('canvas');
  canvas.width = 400; canvas.height = 400;
  canvas.getContext('2d').drawImage(img, 0, 0, 400, 400);
  var pngDataUrl = canvas.toDataURL('image/png');
  // Use pngDataUrl with jsPDF
};
img.src = URL.createObjectURL(blob);

Drawing dotted lines (workaround)

function drawDottedLine(doc, x1, y1, x2, y2) {
  doc.setDrawColor(180, 180, 180);
  doc.setLineWidth(0.01);
  for (var x = x1; x < x2; x += 0.1) {
    doc.line(x, y1, Math.min(x + 0.02, x2), y2);
  }
}

Applicability

Client-side PDF generation works whenever the data is already available in the browser and the layout is programmatic (not complex typesetting). Good for: invoices, tickets, labels, reports, certificates. NOT good for: PDFs that require server-only data, or complex layouts with flowing text, tables, and images (use a server-side tool for those).

Related Lessons

Related Lessons