Tutorial Mar 26, 2026

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

Moving from html-pdf to PageBolt? Here are the 5 most common migration pitfalls and how to solve 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;
}

After (PageBolt):

Same CSS usually works, but test edge cases:

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,
  }),
});

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);
});

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');
}

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;
}

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>

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..." />

Checklist

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%.

Finish your html-pdf migration today

100 requests/month free. No credit card. Works on Node 18+, serverless, containers.

Get API key free →