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!