Skip to main content

Command Palette

Search for a command to run...

PDF Generation: Create PDFs from HTML

Learn: PDF Generation: Create PDFs from HTML

Updated
5 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

PDF Generation from HTML: Puppeteer and Libraries

Problem

Converting HTML to PDF is essential for generating reports, invoices, certificates, and documents programmatically. Manual conversion is time-consuming, and browser-based solutions lack automation. You need a reliable, scalable way to transform HTML content into professional PDFs.

Solution

Use Puppeteer (headless Chrome automation) combined with specialized libraries for robust PDF generation. This approach handles complex layouts, CSS, JavaScript rendering, and provides fine-grained control over output.

Code

1. Basic Puppeteer PDF Generation

const puppeteer = require('puppeteer');
const fs = require('fs');

async function generatePDF() {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Load HTML content
  const htmlContent = `
    <!DOCTYPE html>
    <html>
      <head>
        <style>
          body { font-family: Arial, sans-serif; margin: 20px; }
          h1 { color: #333; }
          .footer { margin-top: 40px; border-top: 1px solid #ccc; }
        </style>
      </head>
      <body>
        <h1>Invoice #12345</h1>
        <p>Date: ${new Date().toLocaleDateString()}</p>
        <table border="1" cellpadding="10">
          <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
          <tr><td>Widget A</td><td>2</td><td>$50</td></tr>
          <tr><td>Widget B</td><td>1</td><td>$75</td></tr>
        </table>
        <div class="footer">
          <p>Thank you for your business!</p>
        </div>
      </body>
    </html>
  `;

  await page.setContent(htmlContent);
  await page.pdf({ path: 'invoice.pdf', format: 'A4' });

  await browser.close();
  console.log('PDF generated: invoice.pdf');
}

generatePDF().catch(console.error);

2. Advanced Configuration with Headers/Footers

const puppeteer = require('puppeteer');

async function generateAdvancedPDF() {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(`
    <html>
      <head>
        <style>
          body { font-size: 14px; line-height: 1.6; }
          .content { padding: 20px; }
        </style>
      </head>
      <body>
        <div class="content">
          <h1>Annual Report 2024</h1>
          <p>This is a comprehensive report with multiple pages.</p>
          <p>Lorem ipsum dolor sit amet...</p>
        </div>
      </body>
    </html>
  `);

  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    margin: {
      top: '1cm',
      right: '1cm',
      bottom: '1cm',
      left: '1cm'
    },
    headerTemplate: `
      <div style="font-size: 12px; width: 100%; text-align: center;">
        Annual Report
      </div>
    `,
    footerTemplate: `
      <div style="font-size: 12px; width: 100%; text-align: center;">
        Page <span class="pageNumber"></span> of <span class="totalPages"></span>
      </div>
    `,
    displayHeaderFooter: true,
    printBackground: true
  });

  await browser.close();
}

generateAdvancedPDF().catch(console.error);

3. HTML File to PDF

const puppeteer = require('puppeteer');
const path = require('path');

async function htmlFileToPDF(htmlFilePath, outputPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Load from file with proper file:// URL
  const fileUrl = `file://${path.resolve(htmlFilePath)}`;
  await page.goto(fileUrl, { waitUntil: 'networkidle2' });

  await page.pdf({
    path: outputPath,
    format: 'A4',
    scale: 1,
    printBackground: true
  });

  await browser.close();
  console.log(`PDF saved to: ${outputPath}`);
}

htmlFileToPDF('./template.html', './output.pdf').catch(console.error);

4. Dynamic Content with Wait Conditions

const puppeteer = require('puppeteer');

async function generateDynamicPDF(data) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  const htmlContent = `
    <!DOCTYPE html>
    <html>
      <head>
        <style>
          body { font-family: 'Segoe UI', sans-serif; }
          .card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }
          .price { font-size: 24px; color: #27ae60; font-weight: bold; }
        </style>
      </head>
      <body>
        <h1>Product Catalog</h1>
        <div id="products"></div>
        <script>
          const data = ${JSON.stringify(data)};
          const html = data.map(p => \`
            <div class="card">
              <h3>\${p.name}</h3>
              <p>\${p.description}</p>
              <div class="price">\${p.price}</div>
            </div>
          \`).join('');
          document.getElementById('products').innerHTML = html;
        </script>
      </body>
    </html>
  `;

  await page.setContent(htmlContent);

  // Wait for JavaScript to render
  await page.waitForSelector('#products', { timeout: 5000 });

  await page.pdf({
    path: 'catalog.pdf',
    format: 'A4',
    printBackground: true
  });

  await browser.close();
}

const productData = [
  { name: 'Laptop', description: 'High-performance device', price: '$999' },
  { name: 'Mouse', description: 'Wireless mouse', price: '$29' }
];

generateDynamicPDF(productData).catch(console.error);

5. Using html2pdf Library (Client-Side Alternative)

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
</head>
<body>
  <div id="content">
    <h1>Invoice</h1>
    <p>Amount: $500</p>
  </div>

  <button onclick="generatePDF()">Download PDF</button>

  <script>
    function generatePDF() {
      const element = document.getElementById('content');
      const opt = {
        margin: 10,
        filename: 'invoice.pdf',
        image: { type: 'jpeg', quality: 0.98 },
        html2canvas: { scale: 2 },
        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
      };
      html2pdf().set(opt).from(element).save();
    }
  </script>
</body>
</html>

6. Batch PDF Generation

const puppeteer = require('puppeteer');
const fs = require('fs').promises;
const path = require('path');

async function batchGeneratePDFs(documents) {
  const browser = await puppeteer.launch();

  for (const doc of documents) {
    const page = await browser.newPage();
    await page.setContent(doc.html);

    await page.pdf({
      path: path.join('pdfs', `${doc.id}.pdf`),
      format: 'A4'
    });

    await page.close();
    console.log(`Generated: ${doc.id}.pdf`);
  }

  await browser.close();
}

const documents = [
  { id: 'invoice-001', html: '<h1>Invoice 001</h1>' },
  { id: 'invoice-002', html: '<h1>Invoice 002</h1>' }
];

batchGeneratePDFs(documents).catch(console.error);

7. Express.js Integration

const express = require('express');
const puppeteer = require('puppeteer');
const app = express();

app.use(express.json());

app.post('/generate-pdf', async (req, res) => {
  try {
    const { html, filename } = req.body;

    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    await page.setContent(html);
    const pdfBuffer = await page.pdf({ format: 'A4' });

    await browser.close();

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.send(pdfBuffer);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Tips

Performance Optimization

  • Reuse browser instance: Create one browser and multiple pages instead of launching new browsers
  • Disable unnecessary features: Use --no-sandbox, --disable-setuid-sandbox for production
  • Set viewport: page.setViewport({ width: 1920, height: 1080 }) for consistent rendering

Quality & Rendering

  • Wait for content: Use waitForNavigation(), waitForSelector(), or waitForFunction() before PDF generation
  • Print background: Set printBackground: true for colored backgrounds and images
  • Scale factor: Adjust scale (0.5-2) for resolution control

Error Handling

try {
  // PDF generation code
} catch (error) {
  console.error('PDF generation failed:', error);
  // Fallback or retry logic
}

Library Comparison

LibraryBest ForProsCons
PuppeteerComplex layouts, JS renderingFull browser control, accurateResource-heavy
html2pdfClient-side, simple docsLightweight, no server neededLimited CSS support
pdfkitCustom PDFs, low-level controlLightweight, flexibleRequires manual layout
wkhtmltopdfServer-side, legacyFast, stableDeprecated, maintenance issues

Security Considerations

  • Sanitize HTML input to prevent injection attacks
  • Validate file paths to prevent directory traversal
  • Implement rate limiting for PDF generation endpoints
  • Use sandboxed environments for untrusted content

Memory Management

```javascript // Close resources properly await page.close(); await browser.close();

// For batch operations, reuse browser const browser = await puppeteer.launch(); // ... multiple pages await browser.close();