Hi Kendo UI Team,
I’m currently using the Kendo React Workbook to export Excel files with around 10,000 main rows, each having multiple nested child rows. Both the main table and each child table have a large number of columns — typically between 60 and 90 columns each.
The export process itself is fast until calling toDataURL(), but the file download takes several minutes, impacting user experience.
I tried using toBlob() as suggested in some docs, but the method does not exist in the current API.
Could you please advise on the best practices or any performance optimizations for exporting large nested datasets with many columns?
Also, is there any alternative API for exporting large files efficiently?
Thanks in advance for your support!
I’m currently using the Kendo React Workbook to export Excel files with around 10,000 main rows, each having multiple nested child rows. Both the main table and each child table have a large number of columns — typically between 60 and 90 columns each.
The export process itself is fast until calling toDataURL(), but the file download takes several minutes, impacting user experience.
I tried using toBlob() as suggested in some docs, but the method does not exist in the current API.
Could you please advise on the best practices or any performance optimizations for exporting large nested datasets with many columns?
Also, is there any alternative API for exporting large files efficiently?
Thanks in advance for your support!
Here's the pseudocode we're using to generate the Excel:
1. Loop through parent rows
2. For each parent row:
- Push a data row with ~60-90 columns
- If children exist:
- Push a sub-header row (indented)
- Loop through children (~1000+ total)
- Push each child row (indented, 60-90 columns each)
Timing Breakdown (measured via console.time):
- Step 1: Generating rows (data -> array of workbook rows): ~1 second
- Step 2: Workbook creation + toDataURL + download: ~116 seconds
Total Export Time: ~118 seconds
const exportDataWithChildrenToExcel = (data, removedColumns, exportRef, fileName) => {
if (!exportRef || !data?.length) return;
const workbookOptions = exportRef.workbookOptions();
const headerStyle = {
background: "#7a7a7a",
color: "#fff",
};
const sample = data[0];
const parentKeys = Object.keys(sample).filter((k) => isValidParentField(k));
const parentColumns = parentKeys.map((key) => ({ field: key, title: key }));
const rows = [];
// Add parent header
rows.push({
type: "header",
cells: parentColumns.map((col) => ({ ...headerStyle, value: col.title })),
});
for (const parentRow of data) {
// Add parent row
rows.push({
type: "data",
cells: parentColumns.map((col) => ({ value: parentRow[col.title] })),
});
const subRows = flatten(getChildrenFromParent(parentRow)); // typically from "results" field
if (subRows.length > 0) {
const subKeys = Object.keys(subRows[0]).filter((k) => !removedColumns.includes(k));
const subColumns = subKeys.map((k) => ({ field: k, title: k }));
// Add sub-header
rows.push({
cells: [{}, ...subColumns.map((col) => ({ ...headerStyle, value: col.title }))],
});
// Add sub-rows
for (const sub of subRows) {
rows.push({
cells: [{}, ...subColumns.map((col) => ({ value: sub[col.title] }))],
});
}
}
}
// Apply rows to sheet
workbookOptions.sheets[0].rows = rows;
new kendo.ooxml.Workbook(workbookOptions)
.toDataURL()
.then((url) => saveAs(url, fileName))
.catch((err) => console.error("Export failed", err));
};