Generate professional invoices, reports, and documents as downloadable PDFs using React components.
Step 1: Install React-PDF
pnpm add @react-pdf/renderer
Step 2: Create an Invoice Template
// components/pdf/InvoiceDocument.tsx
import {
Document,
Page,
Text,
View,
StyleSheet,
Font,
} from "@react-pdf/renderer";
Font.register({
family: "Inter",
src: "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfMZhrib2Bg-4.ttf",
});
const styles = StyleSheet.create({
page: {
padding: 40,
fontFamily: "Inter",
fontSize: 10,
color: "#1a1a1a",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 30,
},
title: {
fontSize: 24,
fontWeight: "bold",
color: "#2563eb",
},
companyInfo: {
textAlign: "right",
fontSize: 9,
color: "#6b7280",
},
section: {
marginBottom: 20,
},
sectionTitle: {
fontSize: 11,
fontWeight: "bold",
marginBottom: 8,
color: "#374151",
},
table: {
borderWidth: 1,
borderColor: "#e5e7eb",
borderRadius: 4,
},
tableHeader: {
flexDirection: "row",
backgroundColor: "#f3f4f6",
padding: 8,
borderBottomWidth: 1,
borderBottomColor: "#e5e7eb",
},
tableRow: {
flexDirection: "row",
padding: 8,
borderBottomWidth: 1,
borderBottomColor: "#e5e7eb",
},
colDescription: { flex: 3 },
colQty: { flex: 1, textAlign: "center" },
colRate: { flex: 1, textAlign: "right" },
colAmount: { flex: 1, textAlign: "right" },
bold: { fontWeight: "bold" },
totalsRow: {
flexDirection: "row",
justifyContent: "flex-end",
marginTop: 4,
paddingHorizontal: 8,
paddingVertical: 4,
},
footer: {
position: "absolute",
bottom: 30,
left: 40,
right: 40,
textAlign: "center",
fontSize: 8,
color: "#9ca3af",
},
});
interface InvoiceItem {
description: string;
quantity: number;
rate: number;
}
interface InvoiceData {
invoiceNumber: string;
date: string;
dueDate: string;
client: {
name: string;
address: string;
email: string;
};
items: InvoiceItem[];
notes?: string;
}
export function InvoiceDocument({ data }: { data: InvoiceData }) {
const subtotal = data.items.reduce(
(sum, item) => sum + item.quantity * item.rate,
0
);
const tax = subtotal * 0.1;
const total = subtotal + tax;
return (
<Document>
<Page size="A4" style={styles.page}>
{/* Header */}
<View style={styles.header}>
<View>
<Text style={styles.title}>INVOICE</Text>
<Text>#{data.invoiceNumber}</Text>
</View>
<View style={styles.companyInfo}>
<Text style={styles.bold}>RCB Software</Text>
<Text>123 Business Street</Text>
<Text>hello@rcbsoftware.com</Text>
</View>
</View>
{/* Client & Dates */}
<View style={{ flexDirection: "row", marginBottom: 20 }}>
<View style={{ flex: 1 }}>
<Text style={styles.sectionTitle}>Bill To</Text>
<Text style={styles.bold}>{data.client.name}</Text>
<Text>{data.client.address}</Text>
<Text>{data.client.email}</Text>
</View>
<View style={{ flex: 1, textAlign: "right" }}>
<Text>Date: {data.date}</Text>
<Text>Due Date: {data.dueDate}</Text>
</View>
</View>
{/* Line Items */}
<View style={styles.table}>
<View style={styles.tableHeader}>
<Text style={[styles.colDescription, styles.bold]}>
Description
</Text>
<Text style={[styles.colQty, styles.bold]}>Qty</Text>
<Text style={[styles.colRate, styles.bold]}>Rate</Text>
<Text style={[styles.colAmount, styles.bold]}>Amount</Text>
</View>
{data.items.map((item, i) => (
<View key={i} style={styles.tableRow}>
<Text style={styles.colDescription}>{item.description}</Text>
<Text style={styles.colQty}>{item.quantity}</Text>
<Text style={styles.colRate}>${item.rate.toFixed(2)}</Text>
<Text style={styles.colAmount}>
${(item.quantity * item.rate).toFixed(2)}
</Text>
</View>
))}
</View>
{/* Totals */}
<View style={{ marginTop: 10 }}>
<View style={styles.totalsRow}>
<Text style={{ width: 100 }}>Subtotal:</Text>
<Text style={{ width: 80, textAlign: "right" }}>
${subtotal.toFixed(2)}
</Text>
</View>
<View style={styles.totalsRow}>
<Text style={{ width: 100 }}>Tax (10%):</Text>
<Text style={{ width: 80, textAlign: "right" }}>
${tax.toFixed(2)}
</Text>
</View>
<View
style={[
styles.totalsRow,
{
borderTopWidth: 1,
borderTopColor: "#e5e7eb",
paddingTop: 8,
},
]}
>
<Text style={[{ width: 100 }, styles.bold]}>Total:</Text>
<Text style={[{ width: 80, textAlign: "right" }, styles.bold]}>
${total.toFixed(2)}
</Text>
</View>
</View>
{/* Notes */}
{data.notes && (
<View style={[styles.section, { marginTop: 30 }]}>
<Text style={styles.sectionTitle}>Notes</Text>
<Text>{data.notes}</Text>
</View>
)}
{/* Footer */}
<Text style={styles.footer}>
Thank you for your business. Payment due within 30 days.
</Text>
</Page>
</Document>
);
}
Step 3: Client-Side Download Button
"use client";
import { pdf } from "@react-pdf/renderer";
import { InvoiceDocument } from "@/components/pdf/InvoiceDocument";
export function DownloadInvoiceButton({ data }: { data: InvoiceData }) {
async function handleDownload() {
const blob = await pdf(<InvoiceDocument data={data} />).toBlob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `invoice-${data.invoiceNumber}.pdf`;
link.click();
URL.revokeObjectURL(url);
}
return (
<button
onClick={handleDownload}
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
>
Download Invoice PDF
</button>
);
}
Step 4: Server-Side PDF Generation (API Route)
// app/api/invoice/[id]/pdf/route.tsx
import { NextRequest, NextResponse } from "next/server";
import { renderToBuffer } from "@react-pdf/renderer";
import { InvoiceDocument } from "@/components/pdf/InvoiceDocument";
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
// Fetch invoice data from database
const invoice = await getInvoiceById(id);
if (!invoice) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const buffer = await renderToBuffer(
<InvoiceDocument data={invoice} />
);
return new NextResponse(buffer, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="invoice-${id}.pdf"`,
},
});
}
Step 5: PDF Preview in Browser
"use client";
import { useState } from "react";
import { PDFViewer } from "@react-pdf/renderer";
import { InvoiceDocument } from "@/components/pdf/InvoiceDocument";
export function InvoicePreview({ data }: { data: InvoiceData }) {
return (
<PDFViewer width="100%" height={600} className="rounded-lg border">
<InvoiceDocument data={data} />
</PDFViewer>
);
}
Step 6: Multi-Page Report
<Document>
{/* Cover Page */}
<Page size="A4" style={styles.page}>
<Text style={styles.title}>Monthly Report</Text>
<Text>{data.period}</Text>
</Page>
{/* Summary Page */}
<Page size="A4" style={styles.page}>
<Text style={styles.sectionTitle}>Executive Summary</Text>
<Text>{data.summary}</Text>
</Page>
{/* Detail Pages - one per section */}
{data.sections.map((section, i) => (
<Page key={i} size="A4" style={styles.page}>
<Text style={styles.sectionTitle}>{section.title}</Text>
{/* section content */}
</Page>
))}
</Document>
Need Document Generation?
We build web applications with PDF generation, invoice systems, and automated document workflows. Contact us to discuss your project.