Faster Headers and Footers on Large PDFs
Adding headers and footers to a very large PDF in a single pass is slow. Process the document in fixed-size page chunks, render each batch, then merge the parts back together to reduce total rendering time while keeping page numbers continuous across the whole document.
Solution
1. Split the page indexes into batches
Pick a chunk size and group the document's page indexes into fixed-size batches, for example 300 pages each. Smaller chunks render faster individually but add more merge work at the end.
2. Copy each batch into its own document
Extract the pages for the current batch with CopyPages, giving you a standalone document to render.
3. Render headers and footers with continuous numbering
Set FirstPageNumber to the chunk's starting page so numbering carries across the whole document instead of restarting at 1 in each chunk.
4. Save each chunk and dispose it
Write each chunk to a temp file and dispose it. Releasing the chunk before the next batch keeps memory from climbing across the run.
5. Merge the chunks into the final PDF
Re-open the temp files, append them in order with AppendPdf, save the result, then delete the temp files.
using IronPdf;
var pdf = PdfDocument.FromFile("input.pdf");
string headerHtml = "<div style='font-size:10px;'>Header</div>";
string footerHtml = "<div style='text-align:right;font-size:10px;'>Page {page} of {total-pages}</div>";
int chunkSize = 300;
var pageChunks = Enumerable.Range(0, pdf.PageCount)
.GroupBy(i => i / chunkSize)
.Select(g => g.ToArray())
.ToList();
var tempFiles = new List<string>();
foreach (var indexes in pageChunks)
{
var chunkPdf = pdf.CopyPages(indexes);
int globalStartPage = indexes.First() + 1;
chunkPdf.AddHtmlHeadersAndFooters(new ChromePdfRenderOptions
{
FirstPageNumber = globalStartPage, // keeps numbering continuous
HtmlHeader = new HtmlHeaderFooter { HtmlFragment = headerHtml, MaxHeight = 40 },
HtmlFooter = new HtmlHeaderFooter { HtmlFragment = footerHtml, MaxHeight = 40 }
});
string tempFile = Path.GetTempFileName() + ".pdf";
chunkPdf.SaveAs(tempFile);
chunkPdf.Dispose();
tempFiles.Add(tempFile);
}
// Merge the chunks back into one document.
var finalPdf = new PdfDocument(tempFiles.First());
foreach (var file in tempFiles.Skip(1))
{
using var part = new PdfDocument(file);
finalPdf.AppendPdf(part);
}
finalPdf.SaveAs("output.pdf");
finalPdf.Dispose();
// Clean up temp files.
foreach (var file in tempFiles)
File.Delete(file);
using IronPdf;
var pdf = PdfDocument.FromFile("input.pdf");
string headerHtml = "<div style='font-size:10px;'>Header</div>";
string footerHtml = "<div style='text-align:right;font-size:10px;'>Page {page} of {total-pages}</div>";
int chunkSize = 300;
var pageChunks = Enumerable.Range(0, pdf.PageCount)
.GroupBy(i => i / chunkSize)
.Select(g => g.ToArray())
.ToList();
var tempFiles = new List<string>();
foreach (var indexes in pageChunks)
{
var chunkPdf = pdf.CopyPages(indexes);
int globalStartPage = indexes.First() + 1;
chunkPdf.AddHtmlHeadersAndFooters(new ChromePdfRenderOptions
{
FirstPageNumber = globalStartPage, // keeps numbering continuous
HtmlHeader = new HtmlHeaderFooter { HtmlFragment = headerHtml, MaxHeight = 40 },
HtmlFooter = new HtmlHeaderFooter { HtmlFragment = footerHtml, MaxHeight = 40 }
});
string tempFile = Path.GetTempFileName() + ".pdf";
chunkPdf.SaveAs(tempFile);
chunkPdf.Dispose();
tempFiles.Add(tempFile);
}
// Merge the chunks back into one document.
var finalPdf = new PdfDocument(tempFiles.First());
foreach (var file in tempFiles.Skip(1))
{
using var part = new PdfDocument(file);
finalPdf.AppendPdf(part);
}
finalPdf.SaveAs("output.pdf");
finalPdf.Dispose();
// Clean up temp files.
foreach (var file in tempFiles)
File.Delete(file);
Imports IronPdf
Imports System.IO
Imports System.Linq
Dim pdf = PdfDocument.FromFile("input.pdf")
Dim headerHtml As String = "<div style='font-size:10px;'>Header</div>"
Dim footerHtml As String = "<div style='text-align:right;font-size:10px;'>Page {page} of {total-pages}</div>"
Dim chunkSize As Integer = 300
Dim pageChunks = Enumerable.Range(0, pdf.PageCount) _
.GroupBy(Function(i) i \ chunkSize) _
.Select(Function(g) g.ToArray()) _
.ToList()
Dim tempFiles = New List(Of String)()
For Each indexes In pageChunks
Dim chunkPdf = pdf.CopyPages(indexes)
Dim globalStartPage As Integer = indexes.First() + 1
chunkPdf.AddHtmlHeadersAndFooters(New ChromePdfRenderOptions With {
.FirstPageNumber = globalStartPage,
.HtmlHeader = New HtmlHeaderFooter With {.HtmlFragment = headerHtml, .MaxHeight = 40},
.HtmlFooter = New HtmlHeaderFooter With {.HtmlFragment = footerHtml, .MaxHeight = 40}
})
Dim tempFile As String = Path.GetTempFileName() & ".pdf"
chunkPdf.SaveAs(tempFile)
chunkPdf.Dispose()
tempFiles.Add(tempFile)
Next
' Merge the chunks back into one document.
Dim finalPdf = New PdfDocument(tempFiles.First())
For Each file In tempFiles.Skip(1)
Using part = New PdfDocument(file)
finalPdf.AppendPdf(part)
End Using
Next
finalPdf.SaveAs("output.pdf")
finalPdf.Dispose()
' Clean up temp files.
For Each file In tempFiles
File.Delete(file)
Next
FirstPageNumber is the key: it is set to each chunk's global starting page, so a footer like Page {page} of {total-pages} stays consistent from the first chunk to the last.
Debug Tips
- Tune the chunk size: the value above (300) is adjustable, and the point at which chunking starts to pay off depends on your workload.
- Account for the merge pass: total time is per-chunk rendering plus the final merge, so factor that extra pass into any comparison.
- Keep memory in check on very large jobs: periodically forcing garbage collection during the merge and tuning the browser pool with
BrowserPool.MaxIdleTabsandMaxDynamicHFPagesPerBatchcan help.

