Hi guys, I ran into the same persistent issue many have mentioned:
“The input directory /var/task/node_modules/@sparticuz/chromium/bin does not exist.”
After days of testing every suggestion (including @sparticuz/chromium, playwright-core, custom vercel.json configs, and the manual postinstall zip script, this is what finally worked for my production setup on Vercel.
My Stack:
- Next.js 16 (App Router)
- Supabase (Postgres + RLS multi-tenant setup)
- Vercel Functions (Node.js 20 runtime)
puppeteer-core @ latest
@sparticuz/chromium-min @ latest (141.0.0)
- Target: Generate PDFs server-side using Puppeteer (not screenshots)
Solution that worked for me:
- Install the latest versions
npm install puppeteer-core @sparticuz/chromium-min
- Import these at the top-level of your API route
// app/api/generate-quote-pdf/route.ts
import "puppeteer-core";
import "@sparticuz/chromium-min";
- Add this to your
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: ["puppeteer-core", "@sparticuz/chromium-min"],
};
export default nextConfig;
- Create your PDF builder file
// server/pdf/buildQuotePDF.cjs
const puppeteerCore = require("puppeteer-core");
const chromium = require("@sparticuz/chromium-min");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const { QuotePDFTemplate } = require("../../components/pdf/QuotePDFTemplate");
async function getBrowser() {
const REMOTE_PATH = process.env.CHROMIUM_REMOTE_EXEC_PATH;
const LOCAL_PATH = process.env.CHROMIUM_LOCAL_EXEC_PATH;
if (!REMOTE_PATH && !LOCAL_PATH) {
throw new Error("Missing a path for Chromium executable");
}
if (REMOTE_PATH) {
// ✅ Use the remote tarball (for Vercel)
return await puppeteerCore.launch({
args: chromium.args,
executablePath: await chromium.executablePath(REMOTE_PATH),
defaultViewport: chromium.defaultViewport,
headless: chromium.headless,
});
}
// ✅ Local fallback (for dev)
return await puppeteerCore.launch({
executablePath: LOCAL_PATH,
headless: true,
});
}
async function buildQuotePDF(quote) {
let browser;
try {
const html = ReactDOMServer.renderToStaticMarkup(
React.createElement(QuotePDFTemplate, { quote })
);
browser = await getBrowser();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle0" });
const pdf = await page.pdf({
format: "A4",
printBackground: true,
margin: { top: "0mm", bottom: "10mm" },
});
return pdf;
} catch (error) {
console.error("❌ Error generating PDF:", error);
throw error;
} finally {
if (browser) await browser.close();
}
}
module.exports = { buildQuotePDF };
- Add these environment variables
In Vercel → Settings → Environment Variables
CHROMIUM_REMOTE_EXEC_PATH=https://github.com/Sparticuz/chromium/releases/download/v141.0.0/chromium-v141.0.0-pack.tar.br
In .env.local (for local testing)
CHROMIUM_LOCAL_EXEC_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
The results I’ve got with my setup:
- It works perfectly fine on localhost via api
- Works in production on Vercel so no more missing binary errors