Skip to main content
No API key, no cloud setup. The dev server runs locally with embedded Puppeteer so you can iterate on templates before deploying.
npx pdfn dev
This opens a preview UI at http://localhost:3456.
FlagDefaultDescription
--port3456Port for the dev server
--openOpen browser automatically
--modeLoad additional .env.[mode] files

How it works

The dev server runs inside your project context:
  • Your dependencies work — import dayjs, lodash, or any package from node_modules
  • Your path aliases work@/components/... resolves from your tsconfig.json
  • Isolated Tailwind stylespdfn-templates/styles.css is auto-detected, separate from your app’s styles
The preview UI shows all top-level .tsx files in your templates directory with zoom controls (fit-to-window or 100%).

Template directory

By default, pdfn looks for templates in pdfn-templates/ at your project root:
your-project/
├── pdfn-templates/
│   ├── invoice.tsx
│   ├── receipt.tsx
│   ├── styles.css
│   └── components/
│       └── Header.tsx
├── next.config.ts
└── package.json
Only top-level .tsx files appear in the preview sidebar. Files are excluded if they:
  • Are inside subdirectories (e.g., components/Header.tsx)
  • Start with _ (e.g., _utils.tsx, _shared.tsx)

PreviewProps for dev preview

Templates use a static PreviewProps property to provide sample data for the dev server preview:
pdfn-templates/invoice.tsx
import { Document, Page } from '@pdfn/react';

interface InvoiceProps {
  number: string;
  customer: string;
  total: number;
}

function Invoice({ number, customer, total }: InvoiceProps) {
  return (
    <Document title={`Invoice ${number}`}>
      <Page size="A4" margin="1in">
        {/* template content */}
      </Page>
    </Document>
  );
}

Invoice.PreviewProps = {
  number: 'INV-001',
  customer: 'Acme Corp',
  total: 148,
} satisfies InvoiceProps;

export default Invoice;
This pattern keeps props required (better type safety) while providing sample data that the dev server uses automatically.

Production usage

In production, pass actual data as props when calling generate():
app/api/invoice/route.tsx
import { pdfn } from '@pdfn/react';
import Invoice from '@/pdfn-templates/invoice';

const client = pdfn();

export async function GET(request: Request) {
  // Fetch real data from your database
  const invoiceData = await db.invoices.findUnique({ where: { id: '123' } });

  // Pass data as props — PreviewProps is ignored in production
  const { data, error } = await client.generate({
    react: <Invoice
      number={invoiceData.number}
      customer={invoiceData.customer}
      total={invoiceData.total}
    />,
  });

  if (error) {
    return Response.json({ error: error.message }, { status: 500 });
  }

  return new Response(data.buffer, {
    headers: { 'Content-Type': 'application/pdf' },
  });
}
Key points:
  • PreviewProps is only used by the dev server (pdfn dev) for preview
  • In production, you pass props directly to the component like any React component
  • Props are required in the interface, so TypeScript ensures you pass all necessary data

Debug overlays

You can enable visual overlays for grid, margins, headers, and page breaks to debug layout issues. See Generate & Render for all options.

FAQ

Use the PreviewProps static property on your template component. The dev server uses this data automatically. See PreviewProps for dev preview.
Two options:
  • Subdirectory: Put shared components in pdfn-templates/components/
  • Underscore prefix: Name files with _ prefix (e.g., _utils.tsx, _shared.tsx)
Both are excluded from the preview sidebar.
A file appears in the preview sidebar if it:
  • Is a .tsx file in the root of pdfn-templates/
  • Has a default export (component function)
  • Imports Document or Page from @pdfn/react
  • Does not start with _
pdfn Cloud renders PDFs on remote servers that cannot access your local filesystem. Use absolute URLs for images and Google Fonts instead of local files. See Styling — Images and Styling — Fonts.

Next steps