I am trying to achieve a very simple pdf generation through Express JS, puppeteer and ejs. I have used the following code on multiple occasions and have achieved similar results.
const router = require('express').Router();
const puppeteer = require('puppeteer');
const {_generateHtml} = require("./utils");
router.post('/offer-letter', async (req, res) => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
const html = await _generateHtml({
doj: req.body.doj,
name: req.body.name,
role: req.body.role,
}, 'offer-template.ejs');
await page.setContent(html, {waitUntil: 'load', timeout: 0});
const pdfBuffer = await page.pdf({
printBackground: true,
});
res.set('Content-Type', 'application/pdf');
res.send(pdfBuffer);
});
module.exports = router;
Here's the generateHtml function as well
const {compile} = require("ejs");
const fs = require("fs");
async function _generateHtml(data, templatePath) {
const template = fs.readFileSync(templatePath, 'utf8');
let html = compile(template);
return html(data);
}
module.exports = {
_generateHtml
}
Here's the folder structure
.env
index.js
node_modules
offer-letter-template-html.html
offer-template.ejs
package-lock.json
package.json
pdfRoutes.js
utils
However, I now find myself with an error message that's quite the headscratcher. How exactly do you work out this one?
throw new Errors_js_1.TimeoutError(`Timed out after waiting ${ms}ms`, { cause });
^
TimeoutError: Timed out after waiting 30000ms
at C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\puppeteer\common\util.js:289:19
at C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:3986:35
at OperatorSubscriber2._this._next (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1055:13)
at Subscriber2.next (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:678:16)
at AsyncAction2.<anonymous> (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:4863:24)
at AsyncAction2._execute (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1974:16)
at AsyncAction2.execute (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:1963:26)
at AsyncScheduler2.flush (C:\work\offer-letter-manager\backend\node_modules\puppeteer-core\lib\cjs\third_party\rxjs\rxjs.js:2235:30)
at listOnTimeout (node:internal/timers:573:17)
at process.processTimers (node:internal/timers:514:7) {
[cause]: undefined
}
Node.js v20.12.1
Here's a sample of ejs file you can use
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div id="page-container">
test
</div>
</body>
</html>
22nd May 2024 - Edit --------------------------------
This is the minimal failing code for everyone trying to replicate the issue.
const puppeteer = require("puppeteer");
async function generatePDF() {
const browser = await puppeteer.launch({headless: true, timeout:0});
const page = await browser.newPage();
await page.setContent("<h1>Hello World</h1>", { waitUntil: 'domcontentloaded' });
console.log("Content set");
const pdfBuffer = await page.pdf({ printBackground: true });
console.log("PDF generated");
await browser.close();
}
generatePDF().catch(console.error);
package.json for the project
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"puppeteer": "^22.9.0"
}
}
I'm unable to reproduce your problem. Here's my code that works just fine on Node 20 and Ubuntu 22.04.
server.js:
// Note: error handling is not included. I don't recommend this setup;
// it's purely for reproduction and doesn't follow best practices.
const fs = require("fs");
const {compile} = require("ejs"); // ^3.1.10
const express = require("express"); // ^4.19.2
const puppeteer = require("puppeteer"); // ^22.7.1
const app = express();
async function _generateHtml(data, templatePath) {
const template = fs.readFileSync(templatePath, "utf8");
const html = compile(template);
return html(data);
}
app.get("/", async (req, res) => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
const html = await _generateHtml({}, "offer-template.ejs");
await page.setContent(html);
const pdfBuffer = await page.pdf({
printBackground: true,
});
res.set("Content-Type", "application/pdf");
res.send(pdfBuffer);
// browser should be closed in a finally
// block here if this were a real script.
});
app.listen(3000);
offer-template.ejs:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div id="page-container">
test
</div>
</body>
</html>
Run node server
, visit your browser at http://localhost:3000 and you'll see the PDF correctly rendered. Since the POST data isn't used in the template (and templating should be irrelevant as far as a hang is concerned--it ultimately returns a string, which you could hardcode for minimal reproducibility), I removed that. Same for the exported module--it's irrelevant.
Please provide more details and steps to reproduce the issue. There's nothing obviously wrong with the code you've shown, other than using timeout: 0, which stifles errors and can cause an infinite hang.