Home > Uncategorized > Generate PDFs from HTML in the Browser: A Developer’s Guide to Client-Side PDF Creation

Generate PDFs from HTML in the Browser: A Developer’s Guide to Client-Side PDF Creation

Have you ever needed to let users download a webpage as a PDF without setting up server-side PDF generation? Whether it’s invoices, reports, or certificates, client-side PDF generation can be a game-changer for web applications. In this post, I’ll show you how to implement reliable PDF generation using JavaScript libraries that work entirely in the browser.

Why Client-Side PDF Generation?

Before diving into the technical implementation, let’s consider why you might choose client-side PDF generation:

Advantages:

  • No server load – Processing happens on the user’s device
  • Instant results – No network round trips for PDF generation
  • Privacy – Sensitive data never leaves the user’s browser
  • Cost effective – No additional server resources needed
  • Works offline – Once the page loads, PDF generation works without internet

Trade-offs:

  • Limited to what the browser can render
  • File sizes may be larger than server-generated PDFs
  • Dependent on the user’s device performance

The Two Main Approaches

Option 1: html2pdf.js (The Simple Approach)

html2pdf.js is a wrapper library that combines html2canvas and jsPDF into an easy-to-use package:// Include the library https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js function generatePDF() { const element = document.querySelector('.content-to-convert'); const options = { margin: 1, filename: 'document.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' } }; html2pdf().set(options).from(element).save(); }

When to use html2pdf.js:

  • Quick prototypes
  • Simple layouts
  • When you need minimal code

Potential issues:

  • Can struggle with localhost development
  • Less control over the conversion process
  • Sometimes has CORS-related problems

Option 2: jsPDF + html2canvas (The Reliable Approach)

For more control and better localhost support, combining jsPDF and html2canvas directly is often more reliable:// Include both libraries https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js function generatePDF() { const element = document.querySelector('.content-to-convert'); // Configure html2canvas const canvasOptions = { scale: 2, useCORS: true, allowTaint: true, backgroundColor: '#ffffff' }; html2canvas(element, canvasOptions).then(canvas => { const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' }); // Calculate dimensions to fit the page const imgData = canvas.toDataURL('image/png'); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const imgWidth = canvas.width; const imgHeight = canvas.height; const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); // Center the image on the page const imgX = (pdfWidth - imgWidth * ratio) / 2; const imgY = 0; pdf.addImage(imgData, 'PNG', imgX, imgY, imgWidth * ratio, imgHeight * ratio); pdf.save('document.pdf'); }).catch(error => { console.error('PDF generation failed:', error); }); }

Real-World Example: Invoice Generation

Let’s walk through a practical example – generating PDF invoices from an ASP.NET Web Forms page. Here’s how I implemented it for a client billing system:

The HTML Structure

<div class="invoice-container"> <table class="header-table"> <tr> <td class="company-info"> <strong>Company Name</strong><br/> Address Line 1<br/> Address Line 2 </td> <td class="invoice-title"> # INVOICE<br/> Invoice #<span id="invoiceId">001</span><br/> Date: <span id="invoiceDate">2025-09-19</span> </td> </tr> </table> <table class="items-table"> <thead> <tr> <th>Description</th> <th>Amount</th> </tr> </thead> <tbody> <tr> <td>Software Development</td> <td>$1,500.00</td> </tr> <tr class="total-row"> <td><strong>Total</strong></td> <td><strong>$1,500.00</strong></td> </tr> </tbody> </table> </div>

The PDF Generation Function

function downloadInvoiceAsPDF() { // Hide UI elements that shouldn't appear in PDF const buttons = document.querySelectorAll('button, input[type="button"]'); buttons.forEach(btn => btn.style.display = 'none'); const element = document.querySelector('.invoice-container'); html2canvas(element, { scale: 2, useCORS: true, allowTaint: true, backgroundColor: '#ffffff' }).then(canvas => { const { jsPDF } = window.jspdf; const pdf = new jsPDF('portrait', 'mm', 'a4'); const imgData = canvas.toDataURL('image/png'); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const imgWidth = canvas.width; const imgHeight = canvas.height; const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); pdf.addImage(imgData, 'PNG', 0, 0, imgWidth * ratio, imgHeight * ratio); // Generate dynamic filename const invoiceId = document.getElementById('invoiceId').textContent; pdf.save(`invoice-${invoiceId}.pdf`); // Restore hidden elements buttons.forEach(btn => btn.style.display = ''); }); }

Best Practices and Tips

1. Optimize for PDF Output

CSS Considerations:/* Use print-friendly styles */ .invoice-container { font-family: Arial, sans-serif; color: #000; background: #fff; } /* Avoid these in PDF content */ .no-pdf { box-shadow: none; border-radius: 0; background-image: none; }

2. Handle Multi-Page Content

For content that spans multiple pages:function generateMultiPagePDF(element) { html2canvas(element, { scale: 2 }).then(canvas => { const { jsPDF } = window.jspdf; const pdf = new jsPDF(); const imgData = canvas.toDataURL('image/png'); const imgWidth = 210; // A4 width in mm const pageHeight = 295; // A4 height in mm const imgHeight = (canvas.height * imgWidth) / canvas.width; let heightLeft = imgHeight; let position = 0; // Add first page pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; // Add additional pages if needed while (heightLeft >= 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save('multi-page-document.pdf'); }); }

3. Error Handling and User Feedback

Always provide feedback to users:async function generatePDFWithFeedback() { try { // Show loading indicator showLoadingSpinner(); const element = document.querySelector('.content'); const canvas = await html2canvas(element, { scale: 2 }); const { jsPDF } = window.jspdf; const pdf = new jsPDF(); const imgData = canvas.toDataURL('image/png'); pdf.addImage(imgData, 'PNG', 10, 10, 190, 0); pdf.save('document.pdf'); showSuccessMessage('PDF downloaded successfully!'); } catch (error) { console.error('PDF generation failed:', error); showErrorMessage('Failed to generate PDF. Please try again.'); } finally { hideLoadingSpinner(); } }

4. Testing Across Browsers

Different browsers may render content slightly differently. Test your PDF generation across:

  • Chrome/Chromium-based browsers
  • Firefox
  • Safari
  • Edge

Performance Considerations

Optimize image processing:

  • Use appropriate canvas scale (2x is usually sufficient)
  • Consider compressing images before PDF generation
  • For large documents, implement progress indicators

Memory management:

  • Clean up canvas elements after use
  • Consider breaking very large documents into smaller chunks

Troubleshooting Common Issues

Problem: PDF is blank or elements are missing

  • Check console for errors
  • Ensure all external resources (fonts, images) are loaded
  • Try reducing the canvas scale

Problem: Poor quality output

  • Increase the canvas scale
  • Use vector-friendly fonts
  • Avoid complex CSS effects

Problem: CORS errors

  • Use useCORS: true and allowTaint: true options
  • Ensure all resources are served from the same domain or have proper CORS headers

Conclusion

Client-side PDF generation is a powerful technique that can enhance user experience while reducing server complexity. The jsPDF + html2canvas approach provides the best balance of reliability and control, especially during development on localhost.

While there are trade-offs compared to server-side solutions, the benefits of instant generation, privacy, and reduced infrastructure costs make it an excellent choice for many web applications.

Whether you’re building invoicing systems, generating reports, or creating certificates, these techniques will help you implement robust PDF generation that works reliably across different environments.

Have you implemented client-side PDF generation in your projects? What challenges did you face, and how did you solve them? Share your experiences in the comments below!

Categories: Uncategorized
  1. No comments yet.
  1. No trackbacks yet.

Leave a comment