DOMINIK FRACKOWIAKWeb Developer
Todas las entradas

React-PDF en Next.js

Hace unos meses, me pidieron en el trabajo que creara un PDF en React/Next. ¿Por qué? Porque el proyecto estaba en Next.js y el PDF debía crearse dinámicamente después de la compra, tomando un número único del backend (prueba de compra). La tarea parecía simple; después de una breve investigación, elegí la biblioteca React-PDF. Sin embargo, rápidamente quedó claro que había problemas. Si te encuentras con problemas similares, este post debería ayudarte a solucionarlos.

Instalación

Debido a que React-PDF es incompatible con versiones más recientes de React.js, es necesario agregar --legacy-peer-deps:

npm install @react-pdf/renderer --save --legacy-peer-deps

Los elementos que utilizamos para construir nuestro documento PDF son Document, Page, View (equivalente a un div), Text, Image. Los importamos:

import { Document, Page, View, Text, Image, PDFViewer, StyleSheet, Font } from '@react-pdf/renderer';

Estos son los únicos bloques de construcción para nuestro PDF.

Las fuentes que queremos usar deben ser "registradas" de la siguiente manera:

Font.register({
family: 'Roboto',
fonts: [
{ src: '/assets/fonts/Roboto-Regular.ttf', fontWeight: 400 },
{ src: '/assets/fonts/Roboto-Medium.ttf', fontWeight: 500 },
{ src: '/assets/fonts/Roboto-Bold.ttf', fontWeight: 700 },
],
});

Estructura del Documento

La estructura de nuestro documento es la siguiente:

const PDF = () => {
return (
<Document>
<Page>
<View>
{/* Tu contenido aquí */}
</View>
</Page>
</Document>
);
};

Por supuesto, como mencioné antes, View aquí es equivalente a un div, y al igual que con un div, lo usas aquí: creas tantos View como necesites, anidando unos dentro de otros o no.

CSS

Escribimos CSS de dos maneras.

Primero: "inline":

<View style={{ display: 'flex', flexDirection: 'row', justifyContent: 'center' }}></View>

Probablemente te sorprenda la notación flexDirection: 'row'. Sí, sé que <code>row</code> es la propiedad predeterminada para flex-direction, pero por mi experiencia, si omitimos esta notación, React-PDF causa problemas.

La segunda forma, probablemente un poco más limpia:

const styles = StyleSheet.create({
imageWrapper: {
marginLeft: 20
}
});
<View style={styles.imageWrapper}></View>

PDF en la Página

Para renderizar el PDF en una página, desde el componente donde creamos el PDF, exportamos la siguiente función:

const PDFView = () => {
return (
<PDFViewer>
<PDF />
</PDFViewer>
);
};

export default PDFView;

A continuación, importamos esta función en el archivo de la página donde se va a mostrar el PDF, pero – ATENCIÓN: IMPORTANTE, si estamos utilizando Next.js – debemos hacerlo de la siguiente manera:

const InvoicePDF = dynamic(() => import('./pdf'), {
ssr: false,
});

Sin olvidar agregar la directiva 'use client' en la primera línea.

El punto es que Next.js renderiza páginas del lado del servidor de forma predeterminada, y nuestro PDF necesita renderizarse del lado del cliente, y debemos encargarnos de eso.

Luego usas InvoicePDF como cualquier otro componente de React:

export default function PDFPage() {
return (
<div className={styles.pdfWrapper}>
<InvoicePDF locale={locale} />
</div>
);
}

Botón de Descarga para el PDF

Si deseas – en lugar de o además de mostrar el PDF completo – insertar un botón para descargarlo, en Next.js tendrás que usar una notación similar para importar el PDF:

const PDFDownloadLink = dynamic(
() => import('@react-pdf/renderer').then(mod => mod.PDFDownloadLink),
{ ssr: false }
);

Y luego envolverlo, por ejemplo, un botón de la sigiente manera:

<PDFDownloadLink
document={<PDF locale={locale} />}
fileName={`DominikFrackowiak_CV_${locale}.pdf`}
>
<button className={className}>
{handleTranslation(locale)}
</button>
</PDFDownloadLink>

¡Voila!