Construindo um combinador de PDF no Cliente em JavaScript
Ao desenvolver ferramentas para documentos, o caminho mais comum é enviar o trabalho pesado para um servidor rodando Node.js ou Python.
Mas isso traz desvantagens: custos altos com servidor, lentidão na conexão e sérios problemas de privacidade para dados sigilosos.
Passando todo esse trabalho de volta para o cliente (Client-Side) no navegador usando JavaScript, você garante extrema velocidade e privacidade. Neste artigo, detalharemos a nossa ferramenta para Juntar PDF.
As Tecnologias
Você precisa de duas coisas principais:
- Framework de UI: React (Next.js)
- Manipulação Lógica de PDF:
pdf-lib(ler, unir e escrever dados PDF) - Renderização Visual de PDF:
pdfjs-dist(PDF.js da Mozilla, para gerar miniaturas visuais)

1. Renderizando Miniaturas via Web Worker
É essencial renderizar as páginas num Web Worker com pdfjs-dist para não travar a UI visual do navegador.
import * as pdfjsLib from "pdfjs-dist";
// Configura o script do worker
pdfjsLib.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
async function renderPdfThumbnail(file: File): Promise<string | null> {
const buf = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: buf }).promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 0.6 });
const canvas = document.createElement("canvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext("2d");
if (!ctx) return null;
await page.render({ canvasContext: ctx, viewport }).promise;
const dataUrl = canvas.toDataURL("image/jpeg", 0.75);
pdf.destroy();
return dataUrl;
}
2. Juntando PDFs com o pdf-lib
Quando o usuário pressiona "Juntar," o manipulador oficial assume:
import { PDFDocument, degrees } from "pdf-lib";
async function mergePdfs(files: { file: File, rotation: number }[]) {
const mergedDoc = await PDFDocument.create();
for (const pdfFile of files) {
const buf = await pdfFile.file.arrayBuffer();
const srcDoc = await PDFDocument.load(buf, { ignoreEncryption: true });
if (pdfFile.rotation !== 0) {
srcDoc.getPages().forEach((p) => {
const currentAngle = p.getRotation().angle;
p.setRotation(degrees((currentAngle + pdfFile.rotation) % 360));
});
}
const copiedPages = await mergedDoc.copyPages(srcDoc, srcDoc.getPageIndices());
copiedPages.forEach((p) => mergedDoc.addPage(p));
}
const mergedBytes = await mergedDoc.save();
const blob = new Blob([mergedBytes], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
}
Conclusão
Essa arquitetura elimina gargalos de privacidade e permite processamento imediato sem envolver redes complexas. Ferramentas totalmente baseadas no navegador agora podem competir abertamente em escala contra softwares de desktop clássicos.