DEV Community

Ram Chandra Giri
Ram Chandra Giri

Posted on

Stop launching Chromium for every PDF — a tiny pool for Puppeteer HTML-to-PDF

My exam platform, mcqplex.com, turns HTML templates (reports, certificates) into PDFs with Puppeteer. Like many tutorials, my code launched and closed a full Chromium for every PDF:

const browser = await puppeteer.launch();
// ...render...
await browser.close();
Enter fullscreen mode Exit fullscreen mode

It worked, but it was slow, and memory jumped when many downloads came in at once.

Keeping one browser warm

const browser = await puppeteer.launch(); // once, at startup

async function renderPdf(html) {
  const context = await browser.createBrowserContext(); // separate cookies/storage
  const page = await context.newPage();
  try {
    await page.setContent(html, { waitUntil: 'domcontentloaded' });
    return await page.pdf({ format: 'A4' });
  } finally {
    await context.close();
  }
}
Enter fullscreen mode Exit fullscreen mode

On my 2-core laptop, a render went from about 1.9 s to about 0.66 s. Your numbers will depend on your machine.

Images still delayed printing

My templates load a school logo from a remote URL. I used domcontentloaded so a slow image host wouldn't block the render, but page.pdf() still waited for the pending image. It waits for fonts to be ready by default, and that didn't finish while the request was hanging.

Calling window.stop() before printing stopped the wait, but it also removed logos that loaded only a little slowly. A short grace period worked better for me:

await page.waitForNetworkIdle({ idleTime: 100, timeout: 3000 }).catch(() => {});
await page.evaluate(() => window.stop());
Enter fullscreen mode Exit fullscreen mode

Slow logos still show up, and a broken one is left blank after 3 seconds. I only found both issues by testing with real Chromium. My mocked tests didn't catch them.

The package

In production I also needed a concurrency limit, a queue limit, timeouts and crash recovery, so I put it all into a small package, pdf-render-pool:

const { createPdfRenderPool } = require('pdf-render-pool');
const { renderPdf } = createPdfRenderPool({ concurrency: 2, maxQueue: 50 });
Enter fullscreen mode Exit fullscreen mode

GitHub: https://github.com/rcgiri-physics/pdf-render-pool

If you try it, I'd be glad to hear how it works for you, especially on Linux servers.

Top comments (0)