Genera PDF en Node.js: Puppeteer, PDFKit y jsPDF
Usa Puppeteer para imprimir un informe HTML, PDFKit para dibujar un documento a partir de datos, o jsPDF con html2canvas para descargar una imagen de un elemento del navegador. Estos ejemplos de código abierto generan PDF con distintas propiedades de texto y diseño, que importan al elegir un método de exportación.
Esto es generación de PDF, no conversión general de documentos: ninguno de estos ejemplos convierte archivos de Word, hojas de cálculo o archivos PDF a otros formatos editables.
Elige la biblioteca adecuada
| Datos de entrada | Ejemplo | Resultado esperado |
|---|---|---|
| HTML local con CSS | Puppeteer | Un diseño de impresión A4 con texto seleccionable |
| Cadenas e instrucciones de dibujo | PDFKit | Un documento A4 con texto seleccionable; tú defines las coordenadas |
| Un elemento del navegador | jsPDF + html2canvas | Una imagen ajustada a una página A4, sin texto seleccionable |
Prepara los ejemplos
Estos ejemplos se probaron en Linux con Node.js 26.8.1,
Yarn 4.12.0 y Chrome for Testing 151.0.7922.71 de Puppeteer. Usa estas versiones probadas para
reproducir el tutorial; no son los requisitos mínimos de las bibliotecas. Necesitas tener Corepack y
unzip instalados antes de crear el proyecto; sigue la
guía de instalación de Yarn si falla corepack --version.
Chrome también necesita las bibliotecas del sistema indicadas en los
requisitos del sistema de Puppeteer.
Pega esto en Bash desde un directorio con permisos de escritura. Crea un proyecto independiente con
cachés locales de paquetes y del navegador, y vuelve a tu directorio original. Si ya existe
node-pdf-demo, se deja intacto. Si la instalación falla después de crear el proyecto,
resuelve el problema indicado y luego repite los dos comandos corepack yarn dentro
de esa carpeta; no necesitas volver a crearla.
(
corepack --version &&
unzip -v >/dev/null &&
mkdir node-pdf-demo &&
cd node-pdf-demo &&
printf '%s\n' '{"private":true,"packageManager":"yarn@4.12.0"}' > package.json &&
touch yarn.lock &&
printf '%s\n' 'nodeLinker: node-modules' 'enableGlobalCache: false' > .yarnrc.yml &&
PUPPETEER_CACHE_DIR="$PWD/.puppeteer" corepack yarn add --exact \
puppeteer@25.5.0 pdfkit@0.17.2 jspdf@4.2.1 html2canvas@1.4.1 &&
PUPPETEER_CACHE_DIR="$PWD/.puppeteer" corepack yarn exec puppeteer browsers install chrome
)
La instalación explícita del navegador funciona incluso cuando los scripts de instalación de las
dependencias están deshabilitados. La guía de instalación de Puppeteer
explica esa diferencia. Guarda los archivos siguientes en node-pdf-demo y ejecuta
los comandos de Node desde ese directorio. La extensión .cjs mantiene los
ejemplos del servidor en CommonJS incluso dentro de un proyecto con módulos ES.
Conversión de HTML a PDF con Puppeteer
Puppeteer usa el motor de renderizado de impresión de Chrome. Su
método page.pdf() usa CSS de impresión de forma
predeterminada, por lo que un PDF puede diferir del diseño en pantalla. Este ejemplo usa únicamente
HTML local y autónomo. Renderiza HTML que controles; no es un servicio para aceptar URL arbitrarias
ni scripts no confiables.
Imprime un informe local
Guarda esto como report.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Puppeteer report</title>
<style>
body { margin: 0; font-family: Arial, sans-serif; }
main { padding: 32px; }
h1 { font-size: 28px; }
.marker { height: 24px; background: #c81e50; }
footer { margin-top: 160px; }
</style>
</head>
<body>
<main>
<h1>Hello, Puppeteer!</h1>
<p>This report keeps its text selectable.</p>
<div class="marker"></div>
<footer>End of report.</footer>
</main>
</body>
</html>
Guarda esto como html-to-pdf.cjs:
const { pathToFileURL } = require('node:url')
const puppeteer = require('puppeteer')
async function htmlToPdf(url, outputPath) {
const browser = await puppeteer.launch()
try {
const page = await browser.newPage()
const response = await page.goto(url, { waitUntil: 'networkidle0' })
if (!response || !response.ok()) {
throw new Error(`Page load failed: ${response?.status() ?? 'no HTTP response'}`)
}
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true,
margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
})
} finally {
await browser.close()
}
}
const inputUrl = pathToFileURL(process.argv[2] ?? 'report.html').href
htmlToPdf(inputUrl, process.argv[3] ?? 'puppeteer.pdf').catch((error) => {
console.error(error.message)
process.exitCode = 1
})
PUPPETEER_CACHE_DIR="$PWD/.puppeteer" node html-to-pdf.cjs report.html puppeteer.pdf
Abre puppeteer.pdf. Debería contener una página, una barra de color y
End of report. debajo del texto principal. Selecciona y copia
Hello, Puppeteer! para comprobar que es texto y no una imagen.
printBackground: true incluye la barra CSS. El HTML más largo se distribuye entre las páginas
de impresión; añade estilos de impresión para definir tus propios saltos de página. Una página
dinámica necesita su propia condición de disponibilidad, ya que la ausencia de actividad de red no
demuestra que hayan finalizado todas las actualizaciones de la aplicación.
Generación de PDF con PDFKit
PDFKit crea contenido mediante sus API de dibujo y texto; no interpreta tu HTML ni tu CSS. Úsalo cuando tengas datos y quieras controlar dónde aparecen en la página. Su guía de primeros pasos describe el flujo del documento y las opciones de página.
Dibuja un documento
Guarda esto como create-pdf.cjs. Al esperar a pipeline(),
se notifican los errores del flujo de salida antes de que termine el programa:
const PDFDocument = require('pdfkit')
const fs = require('node:fs')
const { pipeline } = require('node:stream/promises')
async function createPdf(outputPath) {
const doc = new PDFDocument({ size: 'A4' })
const writing = pipeline(doc, fs.createWriteStream(outputPath))
try {
doc.fontSize(25).text('Hello, PDFKit!', 100, 100)
doc.fontSize(12).text('This is a sample PDF generated using PDFKit.', 100, 150)
doc.text('End of report.', 100, 350)
doc.end()
} catch (error) {
doc.destroy(error)
}
await writing
}
createPdf(process.argv[2] ?? 'pdfkit.pdf').catch((error) => {
console.error(error.message)
process.exitCode = 1
})
node create-pdf.cjs pdfkit.pdf
Abre pdfkit.pdf e intenta copiar Hello, PDFKit! del encabezado.
Las tres líneas de texto deberían caber en una página A4. Las coordenadas se expresan en puntos PDF,
con 72 puntos por pulgada. PDFKit puede ajustar el texto a varias líneas, pero un documento con
tablas o encabezados repetidos sigue necesitando lógica de diseño; una plantilla HTML no se
traslada a estas API.
Ambos comandos de Node reemplazan un destino existente cuando comienza la escritura. Si falta el archivo de entrada o falla el inicio del navegador, un PDF anterior puede quedar intacto; si falla la escritura, puede quedar un archivo parcial. Usa un nombre de archivo nuevo si necesitas conservar una exportación anterior. Para comprobar el manejo de errores, pasa como destino una ruta dentro de un directorio inexistente; el comando debería informar de un error y terminar con un código de salida distinto de cero.
Generación de PDF del lado del cliente con jsPDF
jsPDF también puede crear PDF basados en texto. Aquí, html2canvas renderiza un elemento HTML en un lienzo, y jsPDF incorpora ese lienzo como una imagen. html2canvas reconstruye el elemento a partir de las propiedades de DOM y CSS compatibles; su documentación explica por qué puede diferir de una captura de pantalla del navegador.
Descarga una imagen de un elemento del navegador
Guarda este archivo completo como browser-pdf.html, junto a
node_modules, y ábrelo en Chrome:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Browser PDF export</title>
<style>
#content { box-sizing: border-box; width: 640px; padding: 32px; font: 16px Arial; }
h1 { font-size: 28px; }
.marker { height: 24px; background: #c81e50; }
footer { margin-top: 160px; }
</style>
</head>
<body>
<script src="./node_modules/jspdf/dist/jspdf.umd.min.js"></script>
<script src="./node_modules/html2canvas/dist/html2canvas.min.js"></script>
<main id="content">
<h1>Hello, jsPDF!</h1>
<p>This report is exported as an image.</p>
<div class="marker"></div>
<footer>End of report.</footer>
</main>
<button id="download">Download PDF</button>
<p id="status" role="status"></p>
<script>
async function generatePDF() {
const { jsPDF } = window.jspdf
const content = document.getElementById('content')
const canvas = await html2canvas(content, { scale: 2, backgroundColor: '#ffffff' })
const imgData = canvas.toDataURL('image/png')
const pdf = new jsPDF()
const width = pdf.internal.pageSize.getWidth() - 20
const height = pdf.internal.pageSize.getHeight() - 20
const scale = Math.min(width / canvas.width, height / canvas.height)
pdf.addImage(imgData, 'PNG', 10, 10, canvas.width * scale, canvas.height * scale)
await pdf.save('download.pdf', { returnPromise: true })
}
const button = document.getElementById('download')
button.addEventListener('click', async () => {
if (button.disabled) return
button.disabled = true
const status = document.getElementById('status')
status.textContent = ''
try {
await generatePDF()
status.textContent = 'Download requested.'
} catch {
status.textContent = 'Unable to generate the PDF. Please try again.'
} finally {
button.disabled = false
}
})
</script>
</body>
</html>
Haz clic en Download PDF. El botón permanece deshabilitado mientras
se genera la imagen, y luego la página muestra Download requested.
Ese mensaje significa que se emitió la solicitud de guardado; busca download.pdf
en las descargas de tu navegador. El proyecto jsPDF documenta su
versión para el navegador.
Abre el archivo descargado y revisa el encabezado, la barra de color y el pie de página. Todos deberían verse dentro de los márgenes de la página, pero no puedes seleccionar el encabezado como texto. Este ejemplo ajusta la imagen completa a una página en lugar de dividirla en varias, por lo que los informes largos se ven pequeños. Usa Puppeteer o un diseño basado en texto para obtener texto que permita búsquedas. La imagen tampoco conserva la estructura semántica del HTML original para los lectores de pantalla. Las imágenes externas necesitan una configuración compatible de mismo origen o CORS; este ejemplo no usa imágenes ni fuentes web.
Mantén wkhtmltopdf en flujos de trabajo heredados
wkhtmltopdf convierte HTML a PDF mediante un antiguo motor Qt WebKit. Su repositorio se archivó en enero de 2023 y ya no recibe mantenimiento. Limita las instalaciones existentes a HTML confiable. Las recomendaciones de seguridad del proyecto advierten contra el uso de datos de entrada no confiables. Para una instalación existente, prueba tus propias plantillas confiables con el binario y las fuentes exactos de esa instalación. Los ejemplos anteriores no validan ese motor de renderizado. Usa un motor que reciba mantenimiento, como Puppeteer, para nuevos proyectos de conversión de HTML a PDF.
