Gerar PDFs no Node.js: Puppeteer, PDFKit e jsPDF
Use Puppeteer para imprimir um relatório HTML, PDFKit para desenhar um documento a partir de dados ou jsPDF com html2canvas para baixar uma imagem de um elemento do navegador. Esses exemplos de código aberto produzem PDFs com propriedades de texto e layout diferentes, que importam ao escolher um método de exportação.
Isso é geração de PDFs, não conversão geral de documentos: nenhum desses exemplos converte arquivos Word, planilhas ou arquivos PDF em outros formatos editáveis.
Escolher a biblioteca adequada
| Sua entrada | Exemplo | Saída esperada |
|---|---|---|
| HTML local com CSS | Puppeteer | Um layout de impressão A4 com texto selecionável |
| Strings e instruções de desenho | PDFKit | Um documento A4 com texto selecionável; você define as coordenadas |
| Um elemento do navegador | jsPDF + html2canvas | Uma imagem ajustada a uma página A4, sem texto selecionável |
Configurar os exemplos
Esses exemplos foram testados no Linux com Node.js 26.8.1,
Yarn 4.12.0 e Chrome for Testing 151.0.7922.71 do Puppeteer. Use essas versões testadas para reproduzir
o passo a passo; elas não são os requisitos mínimos das bibliotecas. Você precisa ter Corepack e
unzip instalados antes de criar o projeto; siga o
guia de instalação do Yarn se corepack --version falhar.
O Chrome também precisa das bibliotecas do sistema listadas nos
requisitos de sistema do Puppeteer.
Cole isto no Bash a partir de um diretório com permissão de escrita. O trecho cria um projeto
separado com caches locais de pacotes e do navegador e retorna ao diretório original. Se
node-pdf-demo já existir, ele será mantido intacto. Se a instalação falhar após a
criação, corrija o problema informado e repita os dois comandos corepack yarn dentro
dessa pasta; não é necessário recriá-la.
(
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
)
A instalação explícita do navegador funciona mesmo quando os scripts de instalação das dependências
estão desativados. O guia de instalação do Puppeteer explica essa
distinção. Salve os arquivos abaixo em node-pdf-demo e execute os comandos Node a
partir desse diretório. A extensão .cjs mantém os exemplos de servidor em
CommonJS mesmo dentro de um projeto com módulos ES.
Conversão de HTML em PDF com Puppeteer
Puppeteer usa o renderizador de impressão do Chrome. Seu
método page.pdf() usa CSS de impressão por padrão,
então um PDF pode diferir do layout na tela. Este exemplo usa apenas HTML local e autossuficiente.
Renderize HTML sob seu controle; este não é um serviço para aceitar URLs arbitrárias ou scripts
não confiáveis.
Imprimir um relatório local
Salve isto 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>
Salve isto 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
Abra puppeteer.pdf. Ele deve conter uma página, uma barra colorida e
End of report. abaixo do texto principal. Selecione e copie
Hello, Puppeteer! para conferir que é texto, e não uma imagem.
printBackground: true inclui a barra criada com CSS. Um HTML mais longo se distribui pelas
páginas de impressão; adicione estilos de impressão para definir suas próprias quebras de página.
Uma página dinâmica precisa de uma condição própria para indicar que está pronta, pois a ausência
de atividade de rede não comprova que todas as atualizações da aplicação terminaram.
Gerar PDFs com PDFKit
PDFKit cria conteúdo por meio de suas APIs de desenho e texto; ele não interpreta seu HTML ou CSS. Use-o quando você tiver dados e quiser controlar onde eles aparecem na página. Seu guia de primeiros passos descreve o fluxo do documento e as opções de página.
Desenhar um documento
Salve isto como create-pdf.cjs. A espera por pipeline() permite
informar erros no fluxo de saída antes que o programa termine:
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
Abra pdfkit.pdf e tente copiar
Hello, PDFKit! do título.
As três linhas de texto devem caber em uma página A4. As coordenadas são expressas em pontos PDF,
com 72 pontos por polegada. PDFKit pode quebrar linhas de texto, mas um documento com tabelas ou
cabeçalhos repetidos ainda precisa de lógica de layout; um modelo HTML não é aproveitado nessas APIs.
Ambos os comandos Node substituem um arquivo de destino existente quando a escrita começa. A falta do arquivo de entrada ou uma falha ao iniciar o navegador pode deixar um PDF anterior intacto, e uma falha na escrita pode deixar um arquivo parcial. Use um novo nome de arquivo se precisar preservar uma exportação anterior. Para conferir o tratamento de erros, passe como saída um caminho dentro de um diretório inexistente; o comando deve informar um erro e encerrar com código de saída diferente de zero.
Geração de PDFs no lado do cliente com jsPDF
jsPDF também pode criar PDFs baseados em texto. Aqui, html2canvas renderiza um elemento HTML em um canvas, e jsPDF incorpora esse canvas como imagem. html2canvas reconstrói o elemento a partir das propriedades de DOM e CSS que suporta; sua documentação explica por que o resultado pode diferir de uma captura de tela do navegador.
Baixar uma imagem de um elemento do navegador
Salve este arquivo completo como browser-pdf.html, ao lado de
node_modules, e abra-o no 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>
Clique em Download PDF. O botão permanece desativado
enquanto a imagem é gerada; em seguida, a página mostra
Download requested..
Essa mensagem significa que a solicitação para salvar foi emitida; confira os downloads do navegador
para encontrar download.pdf.
O projeto jsPDF documenta sua versão para navegadores.
Abra o arquivo baixado e confira o título, a barra colorida e o rodapé. Todos devem estar visíveis dentro das margens da página, mas você não pode selecionar o título como texto. Este exemplo ajusta a escala da imagem inteira para caber em uma página, em vez de distribuí-la por várias páginas, por isso relatórios longos ficam pequenos. Use Puppeteer ou um layout baseado em texto para obter texto pesquisável. A imagem também não contém a estrutura semântica do HTML original para leitores de tela. Imagens externas precisam de uma configuração compatível de mesma origem ou CORS; este exemplo não usa imagens nem fontes da web.
Manter wkhtmltopdf em fluxos de trabalho legados
wkhtmltopdf converte HTML em PDF usando um mecanismo antigo do Qt WebKit. Seu repositório foi arquivado em janeiro de 2023, e ele não recebe mais manutenção. Mantenha as instalações existentes limitadas a HTML confiável. As orientações de segurança do projeto alertam contra entradas não confiáveis. Em uma instalação existente, teste seus próprios modelos confiáveis com o binário exato e as fontes dessa instalação. Os exemplos acima não validam esse renderizador. Use um renderizador que receba manutenção, como Puppeteer, para novos trabalhos de conversão de HTML em PDF.
