Users expect to export their data. Here is how to add CSV and PDF export to your Next.js application.
Step 1: Client-Side CSV Export
No library needed for basic CSV:
"use client";
interface ExportData {
headers: string[];
rows: string[][];
}
export function exportToCsv(data: ExportData, filename: string) {
const csvContent = [
data.headers.join(","),
...data.rows.map((row) =>
row
.map((cell) => {
// Escape cells containing commas or quotes
if (cell.includes(",") || cell.includes('"') || cell.includes("\n")) {
return `"${cell.replace(/"/g, '""')}"`;
}
return cell;
})
.join(",")
),
].join("\n");
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${filename}.csv`;
link.click();
URL.revokeObjectURL(url);
}
// Usage in a component
export function ExportButton({ data }: { data: any[] }) {
function handleExport() {
exportToCsv(
{
headers: ["Name", "Email", "Plan", "Revenue"],
rows: data.map((item) => [
item.name,
item.email,
item.plan,
item.revenue.toString(),
]),
},
"customers-export"
);
}
return (
<button
onClick={handleExport}
className="rounded-lg border px-4 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-800"
>
Export CSV
</button>
);
}
Step 2: Server-Side CSV Export (Large Datasets)
// app/api/export/csv/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/db";
import { customers } from "@/db/schema";
export async function GET(req: NextRequest) {
const data = await db.select().from(customers);
const headers = ["ID", "Name", "Email", "Plan", "Created At"];
const rows = data.map((c) =>
[c.id, c.name, c.email, c.plan, c.createdAt.toISOString()].join(",")
);
const csv = [headers.join(","), ...rows].join("\n");
return new NextResponse(csv, {
headers: {
"Content-Type": "text/csv",
"Content-Disposition": 'attachment; filename="customers.csv"',
},
});
}
Step 3: Streaming CSV for Large Exports
// app/api/export/csv-stream/route.ts
import { NextRequest } from "next/server";
export async function GET(req: NextRequest) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Write headers
controller.enqueue(
encoder.encode("ID,Name,Email,Plan,Revenue\n")
);
// Stream rows in batches
let offset = 0;
const batchSize = 1000;
while (true) {
const batch = await db
.select()
.from(customers)
.limit(batchSize)
.offset(offset);
if (batch.length === 0) break;
for (const row of batch) {
const line = `${row.id},${row.name},${row.email},${row.plan},${row.revenue}\n`;
controller.enqueue(encoder.encode(line));
}
offset += batchSize;
}
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/csv",
"Content-Disposition": 'attachment; filename="export.csv"',
"Transfer-Encoding": "chunked",
},
});
}
Step 4: Excel Export
pnpm add xlsx
"use client";
import * as XLSX from "xlsx";
export function exportToExcel(
data: Record<string, any>[],
filename: string
) {
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
// Auto-size columns
const maxWidths = Object.keys(data[0] || {}).map((key) => ({
wch: Math.max(
key.length,
...data.map((row) => String(row[key] ?? "").length)
),
}));
worksheet["!cols"] = maxWidths;
XLSX.writeFile(workbook, `${filename}.xlsx`);
}
// Usage
<button onClick={() => exportToExcel(tableData, "report")}>
Export Excel
</button>
Step 5: Export Dropdown Component
"use client";
import { useState, useRef, useEffect } from "react";
import { exportToCsv } from "@/lib/export-csv";
import { exportToExcel } from "@/lib/export-excel";
interface ExportDropdownProps {
data: Record<string, any>[];
filename: string;
}
export function ExportDropdown({ data, filename }: ExportDropdownProps) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClick(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, []);
return (
<div ref={ref} className="relative">
<button
onClick={() => setOpen(!open)}
className="flex items-center gap-2 rounded-lg border px-4 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-800"
>
Export
<ChevronDownIcon className="h-4 w-4" />
</button>
{open && (
<div className="absolute right-0 mt-1 w-40 rounded-lg border bg-white p-1 shadow-lg dark:border-gray-700 dark:bg-gray-800">
<button
onClick={() => {
exportToCsv(data, filename);
setOpen(false);
}}
className="w-full rounded px-3 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
>
CSV (.csv)
</button>
<button
onClick={() => {
exportToExcel(data, filename);
setOpen(false);
}}
className="w-full rounded px-3 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
>
Excel (.xlsx)
</button>
<button
onClick={() => {
window.open(`/api/export/pdf?type=${filename}`);
setOpen(false);
}}
className="w-full rounded px-3 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700"
>
PDF (.pdf)
</button>
</div>
)}
</div>
);
}
Step 6: Export with Progress
"use client";
import { useState } from "react";
export function LargeExportButton({ endpoint }: { endpoint: string }) {
const [exporting, setExporting] = useState(false);
async function handleExport() {
setExporting(true);
try {
const res = await fetch(endpoint);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
const disposition = res.headers.get("Content-Disposition");
const filename = disposition?.match(/filename="(.+)"/)?.[1] || "export";
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
} finally {
setExporting(false);
}
}
return (
<button
onClick={handleExport}
disabled={exporting}
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
>
{exporting ? "Generating..." : "Export Report"}
</button>
);
}
Need Data Export Features?
We build web applications with data export, reporting, and analytics dashboards. Contact us to discuss your project.