DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

html-pdf Migration: 5 Common Pitfalls and How to Fix Them

html-pdf Migration: 5 Common Pitfalls and How to Fix Them

You've decided to migrate from html-pdf to PageBolt. Good call. But you've hit a wall. Your PDFs look different. CSS isn't rendering. Callbacks are now promises. Here are the 5 biggest migration pitfalls and how to fix them.

Pitfall #1: CSS Rendering Differences (Grid, Flexbox, Z-Index)

The Problem:
Your HTML/CSS worked perfectly in html-pdf. Now with PageBolt, CSS Grid layouts collapse. Flexbox behaves differently. Z-index stacking breaks.

Why it happens:
html-pdf used PhantomJS (2014 WebKit). PageBolt uses a modern Chromium engine. Modern CSS works differently.

The Fix:

Before (html-pdf):

.container {
  display: flex;
  justify-content: space-between;
  gap: 20px;
}
Enter fullscreen mode Exit fullscreen mode

After (PageBolt):
Same CSS usually works, but test edge cases:

  • Ensure all parent containers have explicit height
  • Use flex-wrap: wrap if content overflows
  • Test Z-index with explicit position: relative/absolute/fixed
const response = await fetch('https://pagebolt.dev/api/v1/pdf', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.PAGEBOLT_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html: htmlContent,
    format: 'A4',
    margin: '0.5in',
    // Add explicit viewport width to match your HTML
    width: 1200,
  }),
});
Enter fullscreen mode Exit fullscreen mode

Pitfall #2: Async/Await vs Callback Hell

The Problem:
You're migrating from html-pdf callbacks to promises, but your error handling is all wrong.

Before (html-pdf):

pdf.create(html, options).toFile('invoice.pdf', (err, res) => {
  if (err) {
    console.error('Failed:', err);
    return res.status(500).send('PDF generation failed');
  }
  res.download(res.filename);
});
Enter fullscreen mode Exit fullscreen mode

After (PageBolt):

try {
  const response = await fetch('https://pagebolt.dev/api/v1/pdf', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.PAGEBOLT_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html }),
  });

  if (!response.ok) {
    throw new Error(`PDF generation failed: ${response.status}`);
  }

  const buffer = await response.arrayBuffer();
  res.setHeader('Content-Type', 'application/pdf');
  res.send(Buffer.from(buffer));
} catch (error) {
  console.error('PDF error:', error);
  res.status(500).send('Failed to generate PDF');
}
Enter fullscreen mode Exit fullscreen mode

Key difference: PageBolt returns HTTP status codes. Check response.ok before processing the buffer.

Pitfall #3: Timeout and ENOMEM Errors on Bulk Operations

The Problem:
Generating 1,000 PDFs in parallel crashes with "ENOMEM" or timeout errors.

Why it happens:
You're sending all 1,000 requests at once. Each one waits for a response. The system runs out of memory.

The Fix:

Use a concurrency limit. Generate PDFs in batches of 5-10:

async function generatePDFsBatch(invoices, batchSize = 5) {
  const results = [];

  for (let i = 0; i < invoices.length; i += batchSize) {
    const batch = invoices.slice(i, i + batchSize);

    const promises = batch.map(async (invoice) => {
      const html = renderTemplate(invoice);
      const response = await fetch('https://pagebolt.dev/api/v1/pdf', {
        method: 'POST',
        headers: {
          'x-api-key': process.env.PAGEBOLT_API_KEY,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ html, format: 'A4' }),
      });

      if (!response.ok) throw new Error(`Failed: ${response.status}`);
      return response.arrayBuffer();
    });

    const batchResults = await Promise.all(promises);
    results.push(...batchResults);
  }

  return results;
}
Enter fullscreen mode Exit fullscreen mode

Process 5 at a time instead of 1,000 at once.

Pitfall #4: Font Loading Issues

The Problem:
Your custom fonts (Google Fonts, Typekit, @font-face) aren't loading in the PDF.

The Fix:

Use web-safe fonts or embed fonts directly in the HTML:

<style>
  @font-face {
    font-family: 'CustomFont';
    src: url('data:font/woff2;base64,...') format('woff2');
  }

  body {
    font-family: 'CustomFont', Arial, sans-serif;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Or use system fonts: Arial, Georgia, Courier New, Verdana.

Pitfall #5: Missing Images and External Resources

The Problem:
Images in your HTML show as broken boxes in the PDF.

The Fix:

Use absolute URLs and ensure they're publicly accessible:

<!-- ❌ Won't work (relative path) -->
<img src="/images/logo.png" />

<!-- ✅ Works (absolute URL) -->
<img src="https://example.com/images/logo.png" />

<!-- ✅ Also works (base64 inline) -->
<img src="data:image/png;base64,iVBORw0KGgo..." />
Enter fullscreen mode Exit fullscreen mode

Checklist

  • [ ] Test CSS Grid and Flexbox layouts
  • [ ] Switch to async/await error handling
  • [ ] Implement batch processing for bulk PDFs (max 5-10 concurrent)
  • [ ] Use web-safe fonts or embedded fonts
  • [ ] Convert image paths to absolute URLs
  • [ ] Test with your actual invoice templates before going live

Summary

Migration is straightforward once you understand these 5 pitfalls:

  1. Modern CSS rendering (test your layouts)
  2. Async/await error handling (check response.ok)
  3. Concurrency limits (batch your requests)
  4. Font compatibility (use web-safe fonts)
  5. Image paths (use absolute URLs)

Your html-pdf migration is probably 95% done. These fixes handle the last 5%.

Need help? Try PageBolt free — 100 requests/month, no credit card. Get started →

Top comments (0)