DEV Community

Hichem Bed
Hichem Bed

Posted on

HTML to PDF in Node.js Without the Puppeteer Pain

If you've ever had to generate a PDF from HTML in Node.js, you know the story.

You reach for Puppeteer. The docs look reasonable. Then, three hours later, you're debugging a segfault in a Docker container because Chromium refuses to run without --no-sandbox, your CSS isn't rendering the same as it does in the browser, and your server's memory has ballooned to 800MB handling a single request.

This is not a Puppeteer bash post. Puppeteer is a solid project. The problem is that running a full browser in production is genuinely hard — and for most apps that need to generate PDFs, it's the wrong tool for the job.

Here's a better approach.

The Setup: One Package, One API Key

npm install renderpdfs
Enter fullscreen mode Exit fullscreen mode

Sign up at renderpdfs.com and grab your API key. Free tier gives you 100 PDFs/month, no credit card.

import RenderPDFs from 'renderpdfs';
const client = new RenderPDFs('rpdf_your_key');
Enter fullscreen mode Exit fullscreen mode

Basic HTML to PDF

const pdf = await client.generate({
  html: '<h1>Hello, world</h1><p>This is a PDF.</p>',
});

import { writeFileSync } from 'fs';
writeFileSync('output.pdf', pdf);
Enter fullscreen mode Exit fullscreen mode

That's it. No browser launch, no page lifecycle, no teardown.

A Real-World Example: Invoice PDF from a Template String

import express from 'express';
import RenderPDFs from 'renderpdfs';

const app = express();
const client = new RenderPDFs(process.env.RENDERPDF_API_KEY);

app.post('/invoices/:id/pdf', async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);

  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <style>
          body { font-family: sans-serif; padding: 40px; color: #111; }
          h1 { font-size: 24px; margin-bottom: 4px; }
          .meta { color: #555; font-size: 14px; margin-bottom: 32px; }
          table { width: 100%; border-collapse: collapse; }
          th { text-align: left; border-bottom: 1px solid #ddd; padding: 8px 0; }
          td { padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
          .total { font-weight: bold; font-size: 16px; margin-top: 24px; text-align: right; }
        </style>
      </head>
      <body>
        <h1>Invoice #${invoice.number}</h1>
        <div class="meta">Issued: ${invoice.date} · Due: ${invoice.dueDate}</div>
        <table>
          <thead><tr><th>Item</th><th>Qty</th><th>Price</th></tr></thead>
          <tbody>
            ${invoice.items.map(item => `
              <tr>
                <td>${item.name}</td>
                <td>${item.qty}</td>
                <td>$${item.price}</td>
              </tr>
            `).join('')}
          </tbody>
        </table>
        <div class="total">Total: $${invoice.total}</div>
      </body>
    </html>
  `;

  const pdf = await client.generate({ html });
  res.setHeader('Content-Type', 'application/pdf');
  res.setHeader('Content-Disposition', `attachment; filename="invoice-${invoice.number}.pdf"`);
  res.send(pdf);
});
Enter fullscreen mode Exit fullscreen mode

Want to Store the PDF and Get a URL Back?

const { url } = await client.generate({ html, store: true });

await db.invoices.update(invoice.id, { pdfUrl: url });
await emailService.send({
  to: customer.email,
  subject: `Invoice #${invoice.number}`,
  body: `Download your invoice: ${url}`,
});
Enter fullscreen mode Exit fullscreen mode

Why Not Just Keep Using Puppeteer?

  • Memory: each Chromium instance is ~150–250MB
  • Cold starts: on serverless (Vercel, Lambda), launching a browser is slow and often fails
  • Docker complexity: you need the right Debian deps, flags, and user permissions
  • Maintenance: keeping Puppeteer and Chromium versions in sync is its own ongoing task

An API approach offloads all of that. Your server stays lean. The PDF renders consistently.

Getting Started

npm install renderpdfs
Enter fullscreen mode Exit fullscreen mode

Free plan at renderpdfs.com: 100 PDFs/month, no credit card. Full API docs at renderpdfs.com/docs.

Top comments (0)