Construyendo un combinador de PDF del lado del cliente en JavaScript
Al crear herramientas de manipulación de documentos, lo más fácil suele ser descargar el trabajo pesado a un servidor. Subes los PDFs, el servidor ejecuta un script de Python o Node.js y te devuelve el archivo combinado.
Sin embargo, este enfoque tiene grandes desventajas: altos costos de servidor, lentitud y problemas de privacidad.
Al llevar de este trabajo al lado del cliente usando JavaScript, creas una herramienta muy rápida, segura y barata de mantener. Aquí explicamos la arquitectura técnica de nuestra herramienta para Unir PDF.
Tecnologías usadas
Para construir un buen manipulador de PDF en el navegador, necesitas:
- Framework UI: React (Next.js)
- Manipulación de PDF:
pdf-lib(para leer, unir y escribir datos PDF) - Renderizado de PDF:
pdfjs-dist(PDF.js de Mozilla, usado para generar miniaturas)

1. Renderizando miniaturas con PDF.js
Para dar una buena experiencia, los usuarios deben ver miniaturas de los PDFs. pdfjs-dist es perfecto, pero renderizar PDFs usa mucha CPU. Para evitar congelar el navegador, es crucial usar un Web Worker.
Así renderizamos la primera página a una imagen JPEG:
import * as pdfjsLib from "pdfjs-dist";
// Apuntar al Web 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. Unir PDFs con pdf-lib
Usamos pdf-lib porque copia elegantemente las páginas entre diferentes PDFDocument sin corromper metadatos ni fuentes.
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);
}
Conclusión
Al ejecutar el procesamiento en el navegador, las aplicaciones web modernas ganan enormemente en privacidad, accesibilidad y velocidad. Aprovechar herramientas como pdf-lib y pdfjs-dist nos brinda una base robusta para la manipulación de PDFs.