You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Heroku免费层运行多实例Puppeteer生成PDF超时问题排查求助

Troubleshooting Heroku Timeouts with Puppeteer PDF Generation (Over 2 PDFs)

Hey there, let's dig into why your app is timing out on Heroku when generating more than 2 PDFs—and how to fix it. Your hunch about Heroku's 512MB free tier memory limit is spot-on, but there are specific issues in your code that are exacerbating this problem.

Root Cause Analysis

The biggest issue in your current code is that you're launching a new Puppeteer browser instance for every PDF and never closing them. Each Chromium browser instance is memory-heavy—even in headless mode, it can take 100MB+ of RAM per instance. By the time you get to the 3rd PDF, you're running 3 separate browsers, which quickly eats up Heroku's limited memory, leading to timeouts or crashes.

Additionally, you're not cleaning up the PDF files you generate (Heroku's filesystem is ephemeral, but leaving files around can still contribute to resource bloat), and you might be hitting limits on shared memory with default Puppeteer settings.

Troubleshooting Steps

  • Check Heroku Memory Usage: Use heroku logs --tail to watch for out-of-memory (OOM) errors, or go to your Heroku app's Metrics tab to monitor RAM usage while generating PDFs. You'll likely see it spike to near 512MB when generating the 3rd PDF.
  • Verify Browser Instance Leaks: Look for logs showing how many browser processes are running—if you're not closing them, they'll linger until Heroku kills your dyno.

Fixes & Optimizations

Here are actionable changes to get your app working with more than 2 PDFs:

1. Reuse a Single Browser Instance

Instead of launching a new browser for each PDF, create one browser before the loop and reuse it for all pages. This cuts down on memory overhead drastically.

2. Always Close Pages & Browser

Make sure to close each page after generating the PDF, and close the browser once all PDFs are done. This frees up memory immediately.

3. Add Puppeteer Flags for Heroku Compatibility

Heroku's environment has limitations with shared memory, so add these args to your Puppeteer launch config:

  • --disable-dev-shm-usage: Bypasses the small /dev/shm partition on Heroku, which can cause crashes with multiple pages.
  • --no-sandbox/--disable-setuid-sandbox: You already have these, but they're essential for running Chromium on Heroku.

4. Clean Up Generated PDF Files

Since Heroku's filesystem is temporary, delete each PDF after adding it to the email attachment (or use in-memory generation to avoid writing files at all).

5. Limit Concurrency (If Needed)

If you're generating a lot of PDFs at once, limit how many pages you process concurrently to avoid overwhelming memory.

Modified Code Example

Here's how your code should look with these fixes:

// Launch a single browser instance outside the loop
const browser = await puppeteer.launch({ 
    headless: true, 
    args: [
        "--no-sandbox", 
        "--disable-setuid-sandbox",
        "--disable-dev-shm-usage", // Critical for Heroku
        "--disable-gpu", // Optional, saves more memory
        "--single-process" // Optional, reduces memory footprint (test first)
    ], 
});

try {
    for (let x = 1; x <= numTickets; x++) { 
        console.log(x, " / ", numTickets); 
        let page;
        try { 
            page = await browser.newPage(); 
            // Compile HTML
            const content = await compile(template, { 
                billing, 
                id: `${id}-${x}`, 
                campaign, 
            }); 
            const pdfFilename = `${billing.first_name}-housedoubleup-${id}-${x}.pdf`;
            const pdfPath = `./${pdfFilename}`;

            options.attachments.push({ 
                filename: pdfFilename, 
                path: pdfPath, 
                contentType: "application/pdf", 
            }); 

            await page.setContent(content); 
            await page.emulateMediaType("screen"); 
            await page.pdf({ 
                path: pdfPath, 
                format: "a5", 
                printBackground: true, 
                landscape: true, 
            }); 
            console.log(`PDF ${x} done`);

            // Optional: Delete the file after attaching (free up disk space)
            const fs = require('fs').promises;
            await fs.unlink(pdfPath);
        } catch (e) { 
            console.log(`Error generating PDF ${x} -> `, e); 
        } finally {
            // Always close the page to free memory
            if (page) await page.close();
        }
    }

    // Send email after all PDFs are processed
    await sendEmail(options);
} catch (emailErr) {
    console.log("Error sending email -> ", emailErr);
} finally {
    // Always close the browser when done
    await browser.close();
}

Bonus: In-Memory PDF Generation (Avoid Files Altogether)

Instead of writing PDFs to disk, you can generate them as buffers and attach them directly to the email. This saves disk space and avoids cleanup:

// Replace the page.pdf call with:
const pdfBuffer = await page.pdf({ 
    format: "a5", 
    printBackground: true, 
    landscape: true, 
});

// Then add to attachments as a buffer:
options.attachments.push({ 
    filename: pdfFilename, 
    content: pdfBuffer, 
    contentType: "application/pdf", 
});

This eliminates the need to write and delete files entirely, which is cleaner and more efficient on Heroku.

Final Notes

If you still hit memory limits with these changes, consider upgrading to Heroku's Hobby tier (which has 1GB of RAM) or optimizing your HTML template to reduce the amount of resources Chromium has to load (e.g., remove unnecessary images, CSS, or scripts).

内容的提问来源于stack exchange,提问作者J.Hansen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:49:06