PDF Generation: Create PDFs from HTML
Learn: PDF Generation: Create PDFs from HTML
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-sandboxfor production - Set viewport:
page.setViewport({ width: 1920, height: 1080 })for consistent rendering
Quality & Rendering
- Wait for content: Use
waitForNavigation(),waitForSelector(), orwaitForFunction()before PDF generation - Print background: Set
printBackground: truefor 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
| Library | Best For | Pros | Cons |
| Puppeteer | Complex layouts, JS rendering | Full browser control, accurate | Resource-heavy |
| html2pdf | Client-side, simple docs | Lightweight, no server needed | Limited CSS support |
| pdfkit | Custom PDFs, low-level control | Lightweight, flexible | Requires manual layout |
| wkhtmltopdf | Server-side, legacy | Fast, stable | Deprecated, 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();