jsPDF alternative for HTML to PDF

jsPDF alternative for HTML to PDF

jsPDF alternative for HTML to PDF. html2canvas output is blurry, unselectable images. See when jsPDF is fine and how to get real vector PDFs.

jsPDF is a solid library for drawing PDFs in the browser. The trouble starts when you use it to convert an existing HTML page, usually through doc.html() or html2canvas.

This page explains why those PDFs look wrong, when jsPDF is still right, and how to render HTML with Chrome instead.

Quick answer. Send the HTML from your backend to a hosted Chromium API and you get a vector PDF with selectable text.

curl -X POST https://html-pdf.com/v1/pdf \
  -H "Authorization: Bearer $HTML_PDF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>Invoice #042</h1>","format":"A4"}' \
  -o invoice.pdf

Why jsPDF HTML output looks wrong

This section shows the usual jsPDF pattern and why its output suffers. The common approach is to screenshot a DOM node with html2canvas, then place that image into a jsPDF document.

import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";

const canvas = await html2canvas(document.querySelector("#invoice"), { scale: 2 });
const pdf = new jsPDF({ format: "a4", unit: "mm" });
pdf.addImage(canvas.toDataURL("image/png"), "PNG", 0, 0, 210, (canvas.height * 210) / canvas.width);
pdf.save("invoice.pdf");

That approach has built-in limits.

doc.html() uses html2canvas under the hood, so it has the same rendering limits.

When jsPDF is the better choice

jsPDF is a good fit when you draw the document instead of converting HTML.

If you control every coordinate, jsPDF produces real text and small files. The problems above come from converting HTML.

Comparison

This table compares the two jsPDF approaches with Chrome rendering.

jsPDF + html2canvas jsPDF drawing API Chrome rendering (html-pdf.com)
Runs Browser Browser Server (API)
Selectable text No (image) Yes Yes (vector)
Uses your existing HTML/CSS Approximated No, you redraw it Yes, real Chromium
Flexbox, grid, web fonts Partial N/A Yes
Page breaks (break-inside, @page) No Manual Yes
Works offline Yes Yes No
Cost Free Free 1 credit per page

The alternative: render HTML with Chrome on the server

This section shows how to replace the html2canvas step with real Chrome rendering. Chrome's print engine produces vector PDFs with selectable text, follows @page and break rules, and supports modern CSS.

You can run headless Chrome yourself (Puppeteer, Playwright) or call an API that does it for you.

With html-pdf.com, your frontend calls your own backend route, and the backend calls the API. Never put the API key in browser code. Anyone could read it from the bundle and spend your credits.

Backend route (Node + Express)

import express from "express";

const app = express();
app.use(express.json({ limit: "5mb" }));

app.post("/api/pdf", async (req, res) => {
  const { html } = req.body; // build or validate this server-side in real apps

  const r = await fetch("https://html-pdf.com/v1/pdf", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HTML_PDF_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html, format: "A4", margin: "15mm", max_pages: 20 }),
  });

  if (!r.ok) return res.status(502).send(await r.text());
  res.set("Content-Type", "application/pdf");
  res.send(Buffer.from(await r.arrayBuffer()));
});

app.listen(3001);

React download button

export function DownloadPdf({ targetId }) {
  async function download() {
    const el = document.getElementById(targetId);
    const css = [...document.styleSheets]
      .map((s) => { try { return [...s.cssRules].map((r) => r.cssText).join("\n"); } catch { return ""; } })
      .join("\n");
    const html = `<!doctype html><html><head><style>${css}</style></head><body>${el.outerHTML}</body></html>`;

    const res = await fetch("/api/pdf", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ html }),
    });
    if (!res.ok) throw new Error("PDF failed");
    const url = URL.createObjectURL(await res.blob());
    Object.assign(document.createElement("a"), { href: url, download: "document.pdf" }).click();
    URL.revokeObjectURL(url);
  }
  return <button onClick={download}>Download PDF</button>;
}

In Vue the same download() function works inside a method or <script setup>.

For production, prefer rendering the HTML on the server from your data (see HTML to PDF in React) so users can't send arbitrary markup. Images must use absolute https:// URLs or data URIs, because the renderer can't reach localhost.

Fixing page breaks

Chrome paginates the document, so you control breaks with plain CSS.

tr, .card { break-inside: avoid; }
.new-page { break-before: page; }
@page { size: A4; margin: 15mm; }

More in HTML to PDF page breaks.

Mapping jsPDF options

Your jsPDF settings map to a few API fields.

jsPDF html-pdf.com
new jsPDF({ format: "a4" }) "format": "A4"
orientation: "landscape" "landscape": true
pdf.save("file.pdf") "filename": "file.pdf"
Margins in addImage coordinates "margin": "15mm" or {top,right,bottom,left}

Or skip the server

If you'd rather not run Chrome at all, the hosted API handles it. html-pdf.com is an HTML/URL to PDF API running headless Chromium. Send {"html": "..."} or {"url": "https://..."} to POST https://html-pdf.com/v1/pdf and get a PDF back as binary, base64, or a 24-hour link.

Options cover format, margins, landscape, headers and footers with page numbers, wait_for_expression for JavaScript-rendered pages, and max_pages.

Pricing: 1 credit = 1 PDF page. You get 100 free credits, then packs from $10 for 1,000 pages down to $0.005 per page. Purchased credits never expire. AI assistants can use the remote MCP server.

Get a free API key · API docs · HTML to PDF in JavaScript · Templates

FAQ

Why is my jsPDF PDF blurry?

jsPDF with html2canvas or doc.html() turns your HTML into an image. Raising scale helps a little but makes files bigger. Rendering with Chrome gives you vector text that stays sharp at any zoom.

Can jsPDF keep text selectable when converting HTML?

Not through html2canvas, because each page is a picture. You get selectable text only when you draw with jsPDF's own API, or when you render the HTML with Chrome.

Is there a free jsPDF alternative for HTML to PDF?

html-pdf.com gives you 100 free credits once with no card, and 1 credit = 1 PDF page. Self-hosted Puppeteer or Playwright are free too, but you run the Chrome servers yourself.

Does Chrome rendering support CSS page breaks?

Yes. break-inside: avoid, break-before: page and @page rules all work. Chromium also supports CSS @page margin boxes (Chrome 131+).

This page as markdown · text