Home / Frontend / Extraer y renderizar paginas de PDF con JavaScript moderno
Extraer y renderizar paginas de PDF con JavaScript moderno

Extraer y renderizar paginas de PDF con JavaScript moderno

1 October 2026 · 16 min de lectura

Introduccion al manejo de documentos PDF en el navegador

Los documentos en formato PDF siguen siendo uno de los estandares mas utilizados para compartir informacion estructurada en entornos profesionales y academicos. Aunque existen numerosas herramientas de escritorio para manipular estos archivos, los desarrolladores web necesitan soluciones que funcionen directamente en el navegador sin depender de software externo. En este tutorial se explica de forma detallada como cargar un archivo PDF local, seleccionar un rango de paginas, generar un nuevo documento con esas paginas y mostrarlo al usuario mediante un iframe o un visor nativo del navegador.

El enfoque se centra en el uso de la biblioteca pdf-lib junto con las APIs nativas de JavaScript como FileReader y URL.createObjectURL. Estas herramientas permiten realizar operaciones de extraccion y renderizado sin necesidad de enviar el archivo a un servidor, lo que mejora la privacidad y reduce la latencia. El proceso completo se implementa de manera asincrona para mantener una experiencia de usuario fluida incluso con documentos de varios megabytes.

A lo largo de las siguientes secciones se analizan las principales bibliotecas disponibles, se justifica la eleccion de pdf-lib, se detallan las funciones auxiliares necesarias y se presenta un ejemplo completo listo para integrar en cualquier aplicacion frontend moderna. El codigo se estructura de forma modular para facilitar su reutilizacion y extension en proyectos reales.

Comparativa de bibliotecas JavaScript para trabajo con PDF

Antes de escribir una sola linea de codigo resulta fundamental conocer las opciones disponibles en el ecosistema de JavaScript. Existen dos categorias principales de bibliotecas: aquellas orientadas al renderizado visual y aquellas diseñadas para la creacion y modificacion de documentos. La eleccion correcta depende del objetivo concreto del proyecto.

La biblioteca pdfjs desarrollada por Mozilla destaca por su capacidad de renderizado. Se trata de la misma tecnologia que utiliza el navegador Firefox para mostrar archivos PDF. Su API es sencilla y esta bien documentada, lo que la convierte en una excelente opcion cuando el objetivo principal es mostrar un documento al usuario con controles personalizados. Sin embargo, sus capacidades de modificacion de contenido son limitadas, por lo que no resulta adecuada para tareas de extraccion o fusion de paginas.

En el extremo opuesto se encuentra pdf-lib. Esta biblioteca se especializa en la creacion y modificacion de documentos PDF. Permite cargar un archivo existente, copiar paginas especificas, añadir texto, imagenes o anotaciones y guardar el resultado como un nuevo ArrayBuffer o Uint8Array. Su principal ventaja es que solo depende de JavaScript puro, por lo que funciona tanto en el navegador como en entornos Node.js, Deno o React Native. La documentacion incluye numerosos ejemplos y ofrece soporte completo de tipos para proyectos TypeScript.

Existe otra biblioteca llamada pdfjs (no confundir con la de Mozilla) orientada a la generacion de documentos desde cero. Su API es amigable para principiantes, pero el soporte para modificar archivos existentes aun se encuentra en fase experimental y no siempre produce resultados consistentes. Por esta razon no se recomienda para escenarios de extraccion de paginas.

La biblioteca js-pdf ofrece un conjunto de funcionalidades muy amplio. Puede generar documentos complejos y realizar algunas operaciones de modificacion. No obstante, su API resulta menos intuitiva que la de pdf-lib y la experiencia de desarrollo suele ser mas verbosa. Para tareas especificas de extraccion de paginas, pdf-lib proporciona un codigo mas limpio y mantenible.

Por ultimo, react-pdf esta diseñada exclusivamente para el ecosistema React. Permite declarar documentos mediante componentes JSX, lo que resulta comodo para desarrolladores familiarizados con este framework. Sus capacidades de modificacion de archivos existentes son limitadas, por lo que no se adapta bien al flujo de trabajo descrito en este tutorial.

Despues de evaluar las opciones, pdf-lib se posiciona como la solucion mas adecuada. Combina una API clara, excelente soporte de modificacion y compatibilidad multiplataforma. Ademas, la comunidad mantiene actualizaciones frecuentes y la documentacion oficial cubre la mayoria de los casos de uso comunes.

Preparacion del entorno y lectura de archivos locales

El primer paso consiste en permitir que el usuario seleccione un archivo PDF desde su dispositivo. HTML5 ofrece un elemento input de tipo file que, combinado con el atributo accept, limita la seleccion a archivos con extension .pdf. El evento change de este elemento dispara la logica de procesamiento.

Para convertir el archivo seleccionado en un formato utilizable por pdf-lib se emplea la API FileReader. Esta API trabaja de forma asincrona mediante callbacks. Con el fin de simplificar el codigo se crea una funcion auxiliar que encapsula FileReader dentro de una Promise, permitiendo el uso de async/await.

function readFileAsync(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
            resolve(reader.result);
        };
        reader.onerror = reject;
        reader.readAsArrayBuffer(file);
    });
}

La funcion recibe un objeto File y devuelve una Promise que se resuelve con un ArrayBuffer. El metodo readAsArrayBuffer es el adecuado porque pdf-lib espera datos binarios en este formato. Una vez obtenida la Promise, el manejador del evento change queda de la siguiente manera:

const onFileSelected = async (e) => {
    const fileList = e.target.files;
    if (fileList && fileList.length > 0) {
        const pdfArrayBuffer = await readFileAsync(fileList[0]);
        // Continuar con la extraccion
    }
};

Este patron evita el anidamiento de callbacks y mantiene el flujo de control lineal. Es importante validar que el usuario haya seleccionado al menos un archivo antes de proceder, ya que el atributo multiple no se utiliza en este ejemplo.

Generacion de rangos de paginas para extraccion

La extraccion de paginas requiere indicar a pdf-lib cuales son los indices que se desean copiar. Los indices en pdf-lib comienzan en cero, mientras que los usuarios suelen pensar en numeros de pagina que empiezan en uno. Por esta razon se implementa una funcion auxiliar que genera un array de indices a partir de un rango inclusivo.

function range(start, end) {
    const length = end - start + 1;
    return Array.from({ length }, (_, i) => start + i - 1);
}

La funcion recibe el numero de pagina inicial y final tal como los entiende el usuario. Internamente resta uno a cada valor para obtener el indice correcto. Por ejemplo, range(2, 3) produce el array [1, 2], que corresponde a las paginas segunda y tercera del documento original.

Esta pequena utilidad resulta clave para que la interfaz de usuario pueda trabajar con numeros de pagina naturales sin preocuparse por la indexacion interna de la biblioteca. En una aplicacion real se pueden añadir validaciones adicionales para comprobar que el rango no exceda el numero total de paginas del documento.

Extraccion de paginas con pdf-lib

Con el ArrayBuffer del documento original disponible y la funcion de rango lista, se procede a la extraccion propiamente dicha. El proceso consta de cuatro pasos: cargar el documento fuente, crear un documento nuevo, copiar las paginas deseadas y guardar el resultado.

import { PDFDocument } from "pdf-lib";

async function extractPdfPage(arrayBuff, startPage, endPage) {
    const pdfSrcDoc = await PDFDocument.load(arrayBuff);
    const pdfNewDoc = await PDFDocument.create();
    const pages = await pdfNewDoc.copyPages(
        pdfSrcDoc,
        range(startPage, endPage)
    );
    pages.forEach((page) => pdfNewDoc.addPage(page));
    const newPdf = await pdfNewDoc.save();
    return newPdf;
}

La funcion PDFDocument.load acepta el ArrayBuffer y construye una representacion interna del documento. PDFDocument.create genera un documento vacio. El metodo copyPages recibe el documento origen y un array de indices, devolviendo un array de objetos Page. Cada uno de estos objetos se añade al documento nuevo mediante addPage. Finalmente, save serializa el documento a un Uint8Array.

Este Uint8Array contiene el PDF resultante listo para ser renderizado o descargado. La operacion es completamente en memoria, por lo que no se generan archivos temporales en el sistema de archivos del cliente.

Conversion a Blob y renderizado en el navegador

Una vez obtenido el Uint8Array, el siguiente objetivo es mostrarlo al usuario. La forma mas sencilla consiste en crear un Blob con el tipo MIME application/pdf y generar una URL de objeto mediante URL.createObjectURL. Esta URL puede asignarse al atributo src de un elemento iframe.

function renderPdf(uint8array) {
    const tempBlob = new Blob([uint8array], {
        type: "application/pdf",
    });
    const docUrl = URL.createObjectURL(tempBlob);
    return docUrl;
}

El Blob encapsula los datos binarios y declara el tipo correcto para que el navegador lo interprete como PDF. La URL generada es temporal y se libera automaticamente cuando la pagina se cierra o cuando se invoca URL.revokeObjectURL. En una aplicacion React o similar se almacena esta URL en el estado del componente para que el iframe se actualice de forma reactiva.

const [pdfFileData, setPdfFileData] = useState(null);

// Dentro del manejador
const newPdfDoc = await extractPdfPage(pdfArrayBuffer, 2, 3);
const url = renderPdf(newPdfDoc);
setPdfFileData(url);

El markup correspondiente es sencillo:

<iframe
    style="display: block; width: 100%; height: 90vh;"
    title="Visor PDF"
    src="{pdfFileData}"
    frameborder="0"
    type="application/pdf"
></iframe>

El navegador se encarga de mostrar el visor nativo con controles de zoom, navegacion de paginas e impresion. Si se requiere un control total sobre la interfaz se puede integrar pdfjs de Mozilla, pero para la mayoria de los casos el visor nativo resulta suficiente y evita añadir dependencias adicionales.

Ejemplo completo de implementacion

A continuacion se presenta el codigo integrado de una aplicacion React sencilla que reune todas las piezas descritas. El componente permite seleccionar un archivo, extrae las paginas 2 y 3 por defecto y muestra el resultado en un iframe a pantalla casi completa.

import { useState } from "react";
import { PDFDocument } from "pdf-lib";

export default function VisorPdf() {
    const [pdfFileData, setPdfFileData] = useState(null);

    function readFileAsync(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result);
            reader.onerror = reject;
            reader.readAsArrayBuffer(file);
        });
    }

    function range(start, end) {
        const length = end - start + 1;
        return Array.from({ length }, (_, i) => start + i - 1);
    }

    async function extractPdfPage(arrayBuff, startPage = 2, endPage = 3) {
        const pdfSrcDoc = await PDFDocument.load(arrayBuff);
        const pdfNewDoc = await PDFDocument.create();
        const pages = await pdfNewDoc.copyPages(
            pdfSrcDoc,
            range(startPage, endPage)
        );
        pages.forEach((page) => pdfNewDoc.addPage(page));
        return await pdfNewDoc.save();
    }

    function renderPdf(uint8array) {
        const tempBlob = new Blob([uint8array], {
            type: "application/pdf",
        });
        return URL.createObjectURL(tempBlob);
    }

    const onFileSelected = async (e) => {
        const fileList = e.target.files;
        if (fileList && fileList.length > 0) {
            const pdfArrayBuffer = await readFileAsync(fileList[0]);
            const newPdfDoc = await extractPdfPage(pdfArrayBuffer);
            const url = renderPdf(newPdfDoc);
            setPdfFileData(url);
        }
    };

    return (
        <>
            <input type="file" accept=".pdf" onChange={onFileSelected} />
            {pdfFileData && (
                <iframe
                    style={{ display: "block", width: "100%", height: "90vh" }}
                    title="Visor PDF"
                    src={pdfFileData}
                    frameBorder="0"
                    type="application/pdf"
                />
            )}
        </>
    );
}

El codigo es autocontenido y puede copiarse directamente en un proyecto Next.js, Vite o Create React App. Para entornos de vanilla JavaScript basta con eliminar el estado de React y manipular el DOM de forma manual.

Consideraciones de rendimiento y memoria

Cuando se trabajan con documentos PDF de gran tamaño es importante tener en cuenta el consumo de memoria. Cada ArrayBuffer y Uint8Array ocupa espacio en el heap del navegador. Para documentos de varias decenas de megabytes se recomienda liberar las URLs de objeto tan pronto como dejen de ser necesarias mediante URL.revokeObjectURL.

Otra tecnica util consiste en procesar el archivo por partes si el rango de paginas es conocido de antemano. pdf-lib carga el documento completo en memoria, por lo que documentos extremadamente grandes pueden provocar que el navegador se quede sin recursos. En esos casos conviene valorar una solucion del lado del servidor o el uso de Web Workers para no bloquear el hilo principal.

La operacion de extraccion de paginas PDF es relativamente ligera una vez que el documento esta cargado. El tiempo se concentra principalmente en la deserializacion inicial del ArrayBuffer. En pruebas con documentos de diez a veinte paginas el proceso completo suele completarse en menos de un segundo en hardware moderno.

Extensiones posibles del flujo basico

El ejemplo presentado se limita a un rango fijo de paginas. En una aplicacion real se pueden añadir controles de interfaz para que el usuario indique el rango deseado. Tambien es posible extraer paginas no consecutivas pasando un array arbitrario de indices a copyPages.

Otra extension util consiste en permitir la fusion de paginas procedentes de varios documentos. Basta con cargar cada archivo, copiar las paginas seleccionadas al documento nuevo y guardar el resultado final. pdf-lib soporta esta operacion de forma nativa.

La descarga del documento generado se puede implementar de forma sencilla creando un enlace temporal:

function downloadPdf(uint8array, filename = "documento-extraido.pdf") {
    const blob = new Blob([uint8array], { type: "application/pdf" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
}

Esta funcion se invoca despues de la extraccion y permite al usuario guardar el resultado en su dispositivo sin depender del visor del navegador.

Manejo de errores y casos limite

Es fundamental anticipar situaciones en las que el archivo seleccionado no sea un PDF valido o este corrupto. PDFDocument.load lanza una excepcion en esos casos. Un bloque try/catch alrededor de la operacion de carga permite mostrar un mensaje amigable al usuario.

try {
    const pdfSrcDoc = await PDFDocument.load(arrayBuff);
} catch (error) {
    console.error("El archivo no es un PDF valido o esta danado");
    // Mostrar notificacion al usuario
}

Tambien conviene validar que el rango solicitado no exceda el numero de paginas del documento. El metodo getPageCount de PDFDocument proporciona esta informacion:

const totalPages = pdfSrcDoc.getPageCount();
if (endPage > totalPages) {
    // Ajustar o rechazar el rango
}

Estos chequeos evitan errores en tiempo de ejecucion y mejoran la robustez de la aplicacion.

Integracion en aplicaciones de produccion

En un entorno de produccion se recomienda encapsular la logica de extraccion en un servicio o hook personalizado. Esto facilita las pruebas unitarias y permite reutilizar el codigo en diferentes componentes. Si se utiliza TypeScript, pdf-lib ofrece definiciones de tipos completas que mejoran la experiencia de desarrollo.

Para aplicaciones que deban procesar muchos archivos de forma concurrente se puede considerar el uso de Web Workers. La biblioteca pdf-lib funciona correctamente dentro de un worker, liberando el hilo principal de la interfaz.

La combinacion de lectura asincrona de archivos con la generacion de URLs temporales constituye un patron reutilizable en cualquier aplicacion que necesite previsualizar o manipular documentos binarios en el cliente.

Mejores practicas de seguridad y privacidad

Al trabajar con archivos locales es importante recordar que el contenido nunca abandona el dispositivo del usuario. Esta caracteristica es una ventaja significativa frente a servicios en la nube que requieren subir el documento. No obstante, se debe evitar almacenar los ArrayBuffer en variables globales o en el almacenamiento local del navegador de forma permanente.

Si la aplicacion necesita conservar el documento generado, se recomienda solicitar el consentimiento explicito del usuario y utilizar la API File System Access cuando este disponible. De esta forma se mantiene el control del usuario sobre sus datos.

Alternativas avanzadas de renderizado

Aunque el iframe con el visor nativo es suficiente para la mayoria de los casos, existen escenarios en los que se requiere un control total sobre la presentacion. En esos casos se puede integrar la biblioteca pdfjs de Mozilla. Esta biblioteca permite renderizar cada pagina en un elemento canvas y añadir capas interactivas, anotaciones o controles personalizados.

La integracion implica cargar el Uint8Array resultante de la extraccion como fuente de datos de pdfjs y configurar el renderizado pagina a pagina. El proceso es mas complejo pero ofrece una experiencia de usuario completamente personalizable.

Resumen del flujo de trabajo completo

El proceso descrito se puede resumir en los siguientes pasos secuenciales. El usuario selecciona un archivo mediante un input de tipo file. La funcion readFileAsync convierte el archivo en un ArrayBuffer. La funcion extractPdfPage carga el documento, crea uno nuevo, copia el rango de paginas indicado y devuelve un Uint8Array. La funcion renderPdf transforma ese array en una URL de objeto que se asigna a un iframe. Opcionalmente se ofrece la descarga del resultado.

Cada una de estas funciones es independiente y puede probarse de forma aislada. Esta modularidad facilita el mantenimiento y la evolucion del codigo a medida que aparecen nuevos requisitos.

La capacidad de generar documentos PDF dinamicos directamente en el cliente abre numerosas posibilidades para aplicaciones de gestion documental, firmas electronicas, generacion de reportes o herramientas de productividad. Al eliminar la dependencia de un servidor se reduce la complejidad de la infraestructura y se mejora la privacidad de los datos del usuario.

Optimizacion para diferentes tipos de dispositivos

En dispositivos moviles el consumo de memoria es un factor critico. Se recomienda limitar el tamaño maximo de los archivos aceptados y mostrar mensajes claros cuando el documento supera un umbral razonable. Tambien es aconsejable desactivar la extraccion automatica en conexiones de datos limitadas y ofrecer una opcion de procesamiento bajo demanda.

Las pruebas en distintos navegadores revelan que Chrome, Firefox, Edge y Safari manejan de forma correcta tanto la generacion de Blobs como el renderizado en iframe. Las diferencias se limitan principalmente a la apariencia del visor nativo, que cada motor implementa a su manera.

Estructura de directorios recomendada para el proyecto

Cuando se integra esta funcionalidad en un proyecto mayor conviene organizar el codigo de forma clara. Una estructura tipica puede ser la siguiente:

src/
├── components/
│   └── PdfExtractor/
│       ├── index.jsx
│       ├── usePdfExtractor.js
│       └── styles.css
├── utils/
│   ├── fileReader.js
│   ├── pdfRange.js
│   └── pdfRenderer.js
└── pages/
    └── extractor.jsx

Esta organizacion separa las utilidades puras de los componentes de interfaz y facilita la reutilizacion en otras partes de la aplicacion.

Pruebas unitarias y de integracion

Las funciones auxiliares se prestan facilmente a pruebas unitarias. Se pueden generar ArrayBuffers de prueba a partir de archivos PDF de muestra y verificar que el numero de paginas del documento resultante coincide con el rango solicitado. Las bibliotecas de testing como Jest o Vitest permiten simular FileReader y comprobar el flujo completo sin necesidad de interaccion manual.

Para pruebas de integracion se recomienda utilizar Playwright o Cypress y simular la seleccion de un archivo real. De esta forma se valida que el iframe muestra el contenido esperado y que la descarga genera un archivo valido.

Evolucion futura de las APIs web

Las especificaciones web continuan evolucionando. La API File System Access ya permite leer y escribir archivos de forma mas directa en algunos navegadores. En el futuro es probable que aparezcan APIs nativas para manipulacion de PDF, aunque de momento las bibliotecas de terceros siguen siendo la opcion mas robusta y compatible.

Mantenerse al dia con las actualizaciones de pdf-lib garantiza el acceso a nuevas funcionalidades y correcciones de seguridad. La biblioteca se actualiza con regularidad y la comunidad aporta ejemplos y plugins adicionales.

Conclusiones

La extraccion y renderizado de paginas PDF directamente en el navegador es una capacidad poderosa que puede implementarse con relativamente poco codigo gracias a pdf-lib y las APIs nativas de JavaScript. El flujo descrito permite cargar un documento, seleccionar un rango de paginas, generar un nuevo archivo y mostrarlo al usuario sin abandonar el cliente.

La clave del exito reside en la modularidad de las funciones auxiliares, el uso correcto de ArrayBuffer y Blob, y la atencion a los detalles de indexacion de paginas. Al seguir las practicas recomendadas de manejo de memoria y validacion de errores se obtiene una solucion robusta lista para entornos de produccion.

Este enfoque elimina la necesidad de servicios externos, mejora la privacidad de los datos y ofrece una experiencia de usuario inmediata. Con las bases presentadas es posible extender la funcionalidad hacia fusion de documentos, anotaciones, generacion dinamica de contenido o integracion con firmas electronicas, abriendo un amplio abanico de posibilidades para aplicaciones web modernas.