Skip to content

SSR & Next.js Support

@easypdf/react is fully SSR-safe. There are no browser globals (window, document, navigator) at import time. All PDF generation code is dynamically imported and guarded — it only runs in the browser, inside a user event handler.

You can import from @easypdf/react in any file, including server components, without errors.

Next.js App Router

No setup is required. Use the hook directly inside any "use client" component.

Client components

useEasyPdf is a React hook and must be called in a client component. Mark the file with "use client":

tsx
// app/components/ReportDownloader.tsx
"use client";

import { useEasyPdf } from "@easypdf/react";

export function ReportDownloader() {
  const { pdfRef, downloadPDF, isLoading } = useEasyPdf({
    pageSize: "A4",
    margins: { top: 20, right: 20, bottom: 20, left: 20 },
    footer: { text: "Page {pageNumber} of {totalPages}", align: "center", fontSize: 10 },
  });

  return (
    <div>
      <button onClick={() => downloadPDF(pdfRef, { filename: "report.pdf" })} disabled={isLoading}>
        {isLoading ? "Generating..." : "Download Report"}
      </button>

      <div ref={pdfRef}>
        {/* Report content */}
      </div>
    </div>
  );
}
tsx
// app/page.tsx — server component
import { ReportDownloader } from "./components/ReportDownloader";

export default function Page() {
  return <ReportDownloader />;
}

Using createPDF / createPDFBlob in Next.js

Programmatic PDF generation works the same way — it just needs to be inside a "use client" component and called from a user event:

tsx
"use client";

import { useEasyPdf } from "@easypdf/react";

export function InvoiceExporter({ invoice }: { invoice: { id: string; total: number } }) {
  const { createPDF, createPDFBlob, isLoading } = useEasyPdf();

  const handleDownload = () =>
    createPDF(
      <div style={{ padding: "32px", fontFamily: "Arial, sans-serif" }}>
        <h1>Invoice #{invoice.id}</h1>
        <p>Total: ${invoice.total}</p>
      </div>,
      { filename: `invoice-${invoice.id}.pdf` }
    );

  const handleUpload = async () => {
    const blob = await createPDFBlob(
      <div style={{ padding: "32px", fontFamily: "Arial, sans-serif" }}>
        <h1>Invoice #{invoice.id}</h1>
        <p>Total: ${invoice.total}</p>
      </div>
    );
    // Send blob to your API route
    const formData = new FormData();
    formData.append("file", blob, `invoice-${invoice.id}.pdf`);
    await fetch("/api/invoices/upload", { method: "POST", body: formData });
  };

  return (
    <div style={{ display: "flex", gap: "8px" }}>
      <button onClick={handleDownload} disabled={isLoading}>
        {isLoading ? "Generating..." : "Download Invoice"}
      </button>
      <button onClick={handleUpload} disabled={isLoading}>
        Upload to Server
      </button>
    </div>
  );
}

Why PDF generation must run in the browser

PDF generation depends on browser APIs that do not exist on the server:

  • html2canvas — captures a DOM element by reading its layout and computed styles via browser APIs
  • jsPDF — builds the PDF binary, requires Blob, ArrayBuffer, and other browser globals
  • ReactDOM.createRoot — used in programmatic mode to render JSX off-screen

Calling downloadPDF, createPDF, or createPDFBlob on the server will throw with a clear error message. Always call these methods from user events (button clicks, form submissions) inside "use client" components.

React version support

@easypdf/react supports React 18 and React 19. No extra configuration is needed for either version.

Released under the MIT License.