Implementing Export of Charts/Dashboards to PDF/PNG on Websites
Exporting a dashboard to PDF or PNG requires capturing DOM elements as images and generating documents. Two approaches exist: client-side (via HTML Canvas API) and server-side (via headless Chromium).
Client-Side Export: html2canvas + jsPDF
npm install html2canvas jspdf
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
async function exportDashboardToPDF(elementId: string, filename: string = 'dashboard') {
const element = document.getElementById(elementId);
if (!element) return;
const loader = document.createElement('div');
loader.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.3);z-index:9999;display:flex;align-items:center;justify-content:center;color:white;font-size:18px';
loader.textContent = 'Generating PDF...';
document.body.appendChild(loader);
try {
const canvas = await html2canvas(element, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: '#ffffff'
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: canvas.width > canvas.height ? 'landscape' : 'portrait',
unit: 'px',
format: [canvas.width / 2, canvas.height / 2]
});
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width / 2, canvas.height / 2);
pdf.save(`${filename}_${format(new Date(), 'yyyy-MM-dd')}.pdf`);
} finally {
document.body.removeChild(loader);
}
}
async function exportToPNG(elementId: string, filename: string = 'chart') {
const element = document.getElementById(elementId);
if (!element) return;
const canvas = await html2canvas(element, { scale: 2, backgroundColor: '#ffffff' });
const link = document.createElement('a');
link.download = `${filename}_${format(new Date(), 'yyyy-MM-dd')}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
}
React Hook
function useExport(elementRef: React.RefObject<HTMLElement>) {
const [isExporting, setIsExporting] = useState(false);
const exportToPDF = async (filename?: string) => {
if (!elementRef.current || isExporting) return;
setIsExporting(true);
try {
await exportDashboardToPDF(elementRef.current, filename);
} finally {
setIsExporting(false);
}
};
const exportToPNG = async (filename?: string) => {
if (!elementRef.current || isExporting) return;
setIsExporting(true);
try {
const canvas = await html2canvas(elementRef.current, {
scale: 2, backgroundColor: '#ffffff'
});
downloadCanvas(canvas, filename);
} finally {
setIsExporting(false);
}
};
return { exportToPDF, exportToPNG, isExporting };
}
function DashboardWithExport() {
const dashboardRef = useRef<HTMLDivElement>(null);
const { exportToPDF, exportToPNG, isExporting } = useExport(dashboardRef);
return (
<div>
<div className="flex gap-2 mb-4">
<button onClick={() => exportToPDF('analytics-report')}
disabled={isExporting} className="export-btn">
{isExporting ? '⏳' : '📄'} Export PDF
</button>
<button onClick={() => exportToPNG('dashboard')}
disabled={isExporting} className="export-btn">
{isExporting ? '⏳' : '🖼'} Save PNG
</button>
</div>
<div ref={dashboardRef} id="dashboard-content">
<Charts />
</div>
</div>
);
}
Server-Side Export via Puppeteer
For complex dashboards with SVG/WebGL charts html2canvas may struggle. Server-side rendering:
import puppeteer from 'puppeteer';
app.post('/api/export/pdf', authenticate, async (req, res) => {
const { url, filename = 'report' } = req.body;
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setCookie({
name: 'auth_token',
value: req.token,
domain: 'your-app.com'
});
await page.goto(`${process.env.APP_URL}${url}?export=true`, {
waitUntil: 'networkidle0',
timeout: 30000
});
await page.waitForSelector('[data-loaded="true"]', { timeout: 15000 });
const pdf = await page.pdf({
format: 'A4',
landscape: true,
printBackground: true,
margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' }
});
await browser.close();
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${filename}.pdf"`);
res.send(pdf);
});
Export Data to CSV/Excel
import * as XLSX from 'xlsx';
function exportToExcel(data: Record<string, unknown>[], filename: string) {
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Data');
XLSX.writeFile(workbook, `${filename}.xlsx`);
}
Timeline
Client-side PNG/PDF export + buttons — 1–2 days. Server-side via Puppeteer — 3–5 days.







