Free tools Windows power users keep installed
One-click scans. No signup required.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
If html-pdf works locally but fails on Heroku, start with PhantomJS rather than your HTML template. html-pdf launches a PhantomJS executable; a missing file, a non-executable file, an incompatible binary, or a runtime library failure can all stop PDF generation. The project is no longer maintained and its repository was archived on July 8, 2026. Its maintainers recommend moving to headless Chrome/Puppeteer, so treat a path correction as a short-term repair and migration as the durable solution.
What the Heroku error usually means
html-pdf is a Node wrapper around PhantomJS. Heroku does not provide a universally compatible PhantomJS runtime for every application, and your local operating system may have a different binary, library set, permissions model, or Node.js version.
- “Failed to load PhantomJS module” generally indicates that the module or its executable cannot be loaded.
- Exit code 127 is commonly associated with a command that cannot be found or started, but the complete stack trace is needed before choosing a fix.
- An executable-permission error, a missing shared library, and a JavaScript module-resolution error are different failures and require different repairs.
The node-html-pdf README documents options such as phantomPath and timeout, but a path setting cannot install a missing executable or make an incompatible binary run.
Capture deployment facts before changing code
Record these facts from the failing Heroku release and dyno. Do not assume that the production runtime matches your laptop.
#1 Best Overall
- Copy the first PhantomJS-related error and the full stack trace from the log.
- Record the deployed Node.js version, operating-system/build-image information shown by Heroku, and the app generation (classic Cedar buildpack or Fir Cloud Native Buildpack).
- List the configured buildpacks and their order.
- Note whether dependencies were installed with development packages omitted and whether the failing request runs in a web dyno, worker, or one-off dyno.
- Save one representative HTML input and the PDF requirements it must satisfy: page size, orientation, fonts, headers, footers, local assets, external resources, timeouts, and expected concurrency.
Confirm Heroku is selecting the runtime you expect
Heroku detects a Node.js application from a package.json in the repository root. Declare a major Node.js range under engines.node and keep local development on the same major line. At the researched date, Heroku listed these supported lines:
| Line | Heroku lifecycle label | Production guidance |
|---|---|---|
| 26.x | Current | Use only when your dependencies and application support it. |
| 24.x | Active LTS | Heroku recommends an Active or Maintenance LTS line for production. |
| 22.x | Maintenance LTS | Still supported; verify that all native dependencies build on it. |
These platform lines change. Recheck the Heroku Node.js Support Reference when you deploy. A typical declaration is:
{
"engines": {
"node": "24.x"
}
}
Commit the lockfile and redeploy after changing the major range. Do not change Node versions blindly: a new version can expose a different native-module or PhantomJS incompatibility.
Check that html-pdf and PhantomJS exist on the dyno
Run diagnostics against the same app and release that serves the failing request. Replace APP_NAME with your app name.
heroku run node -p "process.version" -a APP_NAME
heroku run npm ls html-pdf --depth=3 -a APP_NAME
heroku run sh -c 'find node_modules -iname "*phantom*" -maxdepth 5 -print' -a APP_NAME
heroku run sh -c 'test -n "$PHANTOM_PATH" && ls -l "$PHANTOM_PATH" && test -x "$PHANTOM_PATH"' -a APP_NAME
The first command confirms the actual Node version. The dependency command shows whether html-pdf was installed in the deployed release. The search locates a PhantomJS-related file without assuming a particular package layout. The final check is useful only if you intentionally set PHANTOM_PATH; an empty variable means there is nothing to test.
Rank #2
If npm ls reports an omitted or missing package, inspect your package.json, lockfile, and install mode. A package placed only in devDependencies may not be present in a production install. Redeploy with the dependency in the appropriate section, then repeat the checks.
Use phantomPath only when you have verified the binary
When the executable is present, runnable, and compatible, configure its exact deployed path rather than guessing. Keep the value in an environment variable so it can differ between local and Heroku environments.
const pdf = require('html-pdf');
const options = {
timeout: 30000
};
if (process.env.PHANTOM_PATH) {
options.phantomPath = process.env.PHANTOM_PATH;
}
pdf.create(htmlString, options).toBuffer((error, buffer) => {
if (error) {
console.error('PDF generation failed', error);
process.exitCode = 1;
return;
}
// Send buffer in your response or persist it here.
});
Set PHANTOM_PATH only after the dyno check confirms the file exists and has execute permission. If the file is absent, this code merely points to a nonexistent path. If it fails with a loader or shared-library error, a correct path is not enough.
Review buildpacks without assuming a PhantomJS recipe
Heroku buildpacks can make additional binaries available, but the official documentation does not establish a guaranteed PhantomJS buildpack or a package-specific html-pdf recipe. First identify your app generation and current buildpack order.
heroku buildpacks -a APP_NAME
For classic Cedar apps, buildpack configuration is managed through the Heroku buildpack commands and dashboard. Fir apps use Cloud Native Buildpacks and a different configuration model. Follow the applicable instructions in Heroku’s Managing Buildpacks guide rather than copying a command intended for another generation.
Rank #3
- hole punched
- high quality card stock
- 4 pages
- made in USA
- keyboard shortcuts
If you add a buildpack, verify after deployment that the executable is actually on the dyno, executable, and linked against libraries available in that image. A buildpack that installs “a PhantomJS binary” in general is not proof that this binary is compatible with your app, Node version, or Heroku generation.
Recommended Free Tools
Choose a short-term repair or a migration
| Approach | Use it when | Main trade-offs |
|---|---|---|
| Repair the existing html-pdf runtime | The binary is already present and runnable, and the failure is a confirmed path, permission, or deployment omission. | Small code change, but you retain an unmaintained PhantomJS dependency and its compatibility risk. |
| Migrate to headless Chrome/Puppeteer | You need a maintainable production renderer or PhantomJS cannot run on the chosen image. | Requires application changes and careful validation of HTML/CSS, fonts, assets, PDF options, startup cost, and concurrency. |
The project maintainers explicitly state: “This repo isn’t maintained anymore as phantomjs got dreprecated a long time ago. Please migrate to headless chrome/puppeteer.” The spelling is preserved from the README. The repository’s archived, read-only status reinforces that this is a strategic migration rather than a promise of future PhantomJS fixes.
Plan a Puppeteer migration safely
- Inventory every
html-pdfoption you use, including page size, orientation, margins, headers, footers, local-file access, JavaScript delays, and timeouts. - Choose a supported headless-Chrome/Puppeteer installation strategy compatible with your Cedar or Fir app. Do not assume a third-party buildpack works without checking its documentation and the resulting dyno image.
- Render the same fixture HTML in a staging app and compare text, fonts, page breaks, images, external resources, and generated file size.
- Exercise failure paths: a slow resource, a missing asset, a page that never becomes ready, and a timeout.
- Load-test the PDF endpoint at the concurrency your web dynos can sustain. Browser startup and memory use can change your dyno sizing and request timeout decisions.
- Release behind a controlled rollout, retain the old path briefly for rollback, and remove
html-pdfonly after production output and operational metrics are acceptable.
Validate the repaired deployment
Do not stop at a successful HTTP response. Generate PDFs from representative documents and inspect:
- Font availability and fallback glyphs, including non-Latin text.
- Page dimensions, orientation, margins, headers, footers, and page breaks.
- Images, CSS, JavaScript-generated content, and external URLs.
- Local assets and any authorization or cookie requirements.
- Slow pages, timeout behavior, and cleanup after a failed render.
- Concurrent requests, dyno memory, and response latency.
Keep the exact release, Node version, buildpack configuration, and fixture inputs with the validation results so a later platform change can be compared with the known-good deployment.
Troubleshooting branches
| Symptom | Likely layer | Next action |
|---|---|---|
| “Failed to load PhantomJS module” | Dependency resolution or executable discovery | Run npm ls, locate the file on the dyno, and verify the configured path. |
| Exit code 127 | Executable not found or unable to start | Check the path, execute bit, shebang/interpreter, and whether the file exists in the deployed slug. |
| Permission denied | File mode or filesystem placement | Inspect permissions with ls -l; rebuild or package the binary so the dyno can execute it. |
| Shared-library or loader error | Binary/image incompatibility | Read the first loader error, check the Heroku image and buildpack, and stop treating phantomPath as a fix. |
| Works locally, fails only after release | Different Node version, dependency install, buildpack, or environment variable | Compare local and dyno versions and inspect the deployed dependency tree and configuration. |
| PDF is blank or incomplete | Rendering readiness or asset access | Check waits, external-resource access, local-file handling, and timeout settings; compare a captured fixture. |
| Build succeeds but runtime fails | Runtime binary or library availability | Run the executable check from a one-off dyno and inspect the first runtime error rather than the final wrapper message. |
Or skip the browser setup
If your goal is to capture a deployed page rather than maintain a PhantomJS PDF runtime, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL and returns PNG, JPEG, WebP, or PDF; its pre-capture flow can accept cookie banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →For a direct API call, see the ScreenshotNeo documentation:
Rank #4
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-app.herokuapp.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://your-app.herokuapp.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-app.herokuapp.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It supports custom headers, cookies, authorization, waits, selectors, JavaScript, device presets, full-page capture, and PDF controls. Every plan includes all features; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Does a successful Heroku build prove PhantomJS will run?
No. Build completion confirms that the slug was assembled; the executable can still be absent, non-executable, or incompatible at runtime. Check it from a dyno.
Should I switch Node.js versions before checking the error?
No. First record the deployed version and compare it with local development. Change the major line only when dependency compatibility gives you a reason, then retest the complete PDF fixture set.
What should a migration acceptance record contain?
Keep the renderer version, Heroku generation, buildpack configuration, fixture HTML, PDF options, and comparisons for fonts, assets, pagination, timeout behavior, and concurrent requests.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

