This is part of the series of introducing Vanilla ASP.NET Web Forms Architecture. Read more about this at the main menu: Main Article: [Introducing Vanilla ASP.NET Web Forms Architecture]
Part 4: Generate PDF for The Generated Print Content
- Part 1: Printing Static Layout Content (Tickets, PVC ID Card, etc…)
- Part 2: Printing Semi Dynamic Layout Content (Invoices, Bills, etc…)
- Part 3: Printing Full Dynamic Layout Content (Reports, Data Grids, etc…)
- Part 4.1: Generate PDF Using Chrome.exe in ASP.NET Web Forms
- Part 4.2: Generate PDF Using Puppeteer Sharp in ASP.NET Web Forms
Introduction
In Parts 1-3, we covered generating HTML documents for printing directly from the browser. But what if you need to:
- Email the invoice as a PDF attachment
- Provide a download link for the report
- Archive documents in PDF format
- Generate PDFs server-side without user interaction
This is where HTML-to-PDF conversion comes in. In this part, we’ll cover two approaches:
| Approach | Chrome.exe | Puppeteer Sharp |
|---|---|---|
| What it is | Google Chrome browser in headless mode | .NET library wrapping Chromium |
| Rendering Engine | Chrome (Blink) | Chromium (Blink) |
| Installation | Requires Chrome installed on server | Downloads Chromium automatically (~150MB) |
| Hosting Requirement | Own server / VPS only | Works anywhere (including shared hosting) |
| Process Model | Spawns external process | In-process with managed wrapper |
| Control Level | Command-line arguments only | Full programmatic control |
| Best For | Simple setups, existing Chrome installations | Production applications, more control needed |
Important Note: Using Chrome.exe requires you to own or control the server (dedicated server, VPS, or on-premise). It does not work on shared hosting because you cannot install or execute Chrome.exe on shared hosting environments.
In this article (Part 4.1), we’ll focus on Chrome.exe. Part 4.2 will cover Puppeteer Sharp.
Chapter 1: Using Chrome.exe Command Line
How Chrome Headless PDF Generation Works
Google Chrome has a headless mode — it can run without a visible UI, perfect for server-side operations. When you run Chrome with the --print-to-pdf flag, it:
- Launches Chrome in headless mode (no window)
- Navigates to the specified URL
- Renders the page completely (HTML, CSS, images, fonts)
- Generates a PDF using Chrome’s print engine
- Saves the PDF to the specified path
- Exits automatically
The result is a pixel-perfect PDF that looks exactly like what you’d get from Chrome’s “Print to PDF” feature.
Important: Chrome Headless Mode Changes
Since Chrome 112 (March 2023), Chrome has unified headless and headful modes. The old separate headless implementation was merged with regular Chrome. This means:
- You only need
--headless(not--headless=new) - Since Chrome 132, the old headless mode is only available as a separate binary called
chrome-headless-shell - The rendering is now identical to regular Chrome
Command Line Syntax
chrome.exe --headless --disable-gpu --print-to-pdf="<output-path>" "<url>"
Parameter Reference (Updated 2024)
Core Parameters:
| Parameter | Description |
|---|---|
--headless | Run Chrome without a visible window (required for server-side) |
--disable-gpu | Disable GPU hardware acceleration (recommended for servers) |
--print-to-pdf="path" | Output path for the generated PDF file (default: output.pdf in current directory) |
"url" | The URL or file path to convert to PDF |
PDF Output Options:
| Parameter | Description |
|---|---|
--no-pdf-header-footer | Remove default header (date/time) and footer (URL/page number) — New flag name |
--print-to-pdf-no-header | Legacy flag name (use for older Chrome versions < 120) |
Timing & Performance:
| Parameter | Description |
|---|---|
--timeout=5000 | Maximum wait time in milliseconds before capturing the page |
--virtual-time-budget=5000 | Fast-forward time-dependent code (setTimeout/setInterval) — useful for dynamic pages |
Server/Security Options:
| Parameter | Description |
|---|---|
--no-sandbox | Disable sandbox (may be required on some servers, use with caution) |
--disable-software-rasterizer | Disable software rasterizer |
--run-all-compositor-stages-before-draw | Ensure all rendering is complete before PDF generation |
--allow-chrome-scheme-url | Required to access chrome:// URLs (Chrome 123+) |
Screenshot Options (for reference):
| Parameter | Description |
|---|---|
--screenshot | Take a screenshot instead of PDF (saves as screenshot.png) |
--window-size=1280,720 | Set viewport size for screenshots |
Common Chrome.exe Paths
Windows:
C:\Program Files\Google\Chrome\Application\chrome.exe
C:\Program Files (x86)\Google\Chrome\Application\chrome.exeLinux:
/usr/bin/google-chrome
/usr/bin/chromium-browsermacOS:
/Applications/Google Chrome.app/Contents/MacOS/Google ChromeBasic Command Line Examples
Example 1: Convert a web URL to PDF
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --print-to-pdf="D:\output\invoice.pdf" "https://www.mywebsite.com/invoice/10001"Example 2: Convert a local HTML file to PDF
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --print-to-pdf="D:\output\report.pdf" "file:///D:/www/mywebsite/reports/daily-sales.html"Example 3: With no header/footer (recommended for invoices/reports)
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --no-pdf-header-footer --print-to-pdf="D:\output\invoice.pdf" "https://www.mywebsite.com/invoice/10001"Example 4: With timeout for slow-loading pages
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --no-pdf-header-footer --timeout=10000 --print-to-pdf="D:\output\report.pdf" "https://www.mywebsite.com/report/daily"Example 5: For pages with animations/timers (fast-forward time)
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --virtual-time-budget=5000 --print-to-pdf="D:\output\chart.pdf" "https://www.mywebsite.com/dashboard"Note: If you’re using an older Chrome version (< 120), use --print-to-pdf-no-header instead of --no-pdf-header-footer.
C# Code to Execute Chrome.exe
using System.Diagnostics;
using System.IO;
public class PdfGenerator
{
// ============================================
// Configuration: Path to Chrome executable
// Adjust this path based on your server setup
// ============================================
private static readonly string ChromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";
/// <summary>
/// Generate PDF from a URL using Chrome headless
/// </summary>
/// <param name="sourceUrl">The URL to convert to PDF</param>
/// <param name="outputPdfPath">Full path where PDF will be saved</param>
/// <param name="timeoutMs">Chrome's internal timeout in milliseconds (default: 30000)</param>
/// <param name="removeHeaderFooter">Remove PDF header/footer (default: true)</param>
/// <returns>True if successful, false otherwise</returns>
public static bool GeneratePdf(string sourceUrl, string outputPdfPath,
int timeoutMs = 30000, bool removeHeaderFooter = true)
{
try
{
// ============================================
// Build the command line arguments
// --headless: Run without visible window
// --disable-gpu: Recommended for servers
// --no-pdf-header-footer: Remove date/URL from PDF
// --timeout: Max wait time for page load
// ============================================
string headerFlag = removeHeaderFooter ? "--no-pdf-header-footer" : "";
string arguments = $"--headless --disable-gpu {headerFlag} --timeout={timeoutMs} --print-to-pdf=\"{outputPdfPath}\" \"{sourceUrl}\"";
// ============================================
// Configure the process
// ============================================
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = ChromePath,
Arguments = arguments,
UseShellExecute = false, // Required for redirection
RedirectStandardOutput = true, // Capture output
RedirectStandardError = true, // Capture errors
CreateNoWindow = true // Don't show command window
};
// ============================================
// Execute Chrome and wait for completion
// Add extra buffer time beyond Chrome's internal timeout
// ============================================
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.Start();
// Wait for Chrome to finish
// Process timeout = Chrome timeout + 5 seconds buffer
int processTimeout = timeoutMs + 5000;
bool completed = process.WaitForExit(processTimeout);
if (!completed)
{
// Process timed out - kill it
process.Kill();
return false;
}
// Check if PDF was created successfully
return File.Exists(outputPdfPath);
}
}
catch (Exception ex)
{
// Log the error (implement your logging here)
System.Diagnostics.Debug.WriteLine($"PDF generation failed: {ex.Message}");
return false;
}
}
}Transmitting the PDF for Download
Once the PDF is generated, you can serve it for download in ASP.NET Web Forms:
/// <summary>
/// Send a PDF file to the browser for download
/// </summary>
/// <param name="pdfPath">Full path to the PDF file</param>
/// <param name="downloadFileName">The filename shown in the download dialog</param>
public void TransmitPdfForDownload(string pdfPath, string downloadFileName)
{
// ============================================
// Check if the file exists
// ============================================
if (!File.Exists(pdfPath))
{
Response.StatusCode = 404;
Response.Write("PDF not found");
return;
}
// ============================================
// Read the PDF file into memory
// ============================================
byte[] pdfBytes = File.ReadAllBytes(pdfPath);
// ============================================
// Set response headers for PDF download
// ============================================
// Clear any existing response content
Response.Clear();
// Set content type to PDF
Response.ContentType = "application/pdf";
// Set content disposition for download
// "attachment" = download dialog
// "inline" = display in browser (if browser supports PDF viewing)
Response.AddHeader("Content-Disposition", $"attachment; filename=\"{downloadFileName}\"");
// Set content length for progress indication
Response.AddHeader("Content-Length", pdfBytes.Length.ToString());
// ============================================
// Write the PDF bytes to response
// ============================================
Response.BinaryWrite(pdfBytes);
// End the response
Response.End();
}Complete Example – API Page for PDF Download:
// apiInvoicePdf.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
// ============================================
// Step 1: Authentication check
// ============================================
if (Session["login_user"] == null)
{
Response.StatusCode = 401;
return;
}
// ============================================
// Step 2: Get invoice ID from query string
// ============================================
int invoice_id = 0;
if (!int.TryParse(Request["id"], out invoice_id) || invoice_id <= 0)
{
Response.StatusCode = 400;
return;
}
// ============================================
// Step 3: Define paths
// ============================================
string pdfFolder = Server.MapPath("~/pdf/");
string pdfFileName = $"invoice-{invoice_id}.pdf";
string pdfPath = Path.Combine(pdfFolder, pdfFileName);
// Ensure the pdf folder exists
if (!Directory.Exists(pdfFolder))
{
Directory.CreateDirectory(pdfFolder);
}
// ============================================
// Step 4: Generate PDF using Chrome
// ============================================
string sourceUrl = $"https://www.mywebsite.com/apiInvoice?invoice_id={invoice_id}&autoprint=0";
bool success = PdfGenerator.GeneratePdf(sourceUrl, pdfPath);
if (!success)
{
Response.StatusCode = 500;
Response.Write("PDF generation failed");
return;
}
// ============================================
// Step 5: Send PDF to browser for download
// ============================================
TransmitPdfForDownload(pdfPath, pdfFileName);
// ============================================
// Step 6: Optionally delete the PDF after sending
// (to save disk space)
// ============================================
// File.Delete(pdfPath);
}Chapter 2: Methods of Delivering HTML to Chrome
Chrome.exe needs a URL to access the HTML content. There are two main approaches:
Method 1: Physical HTML File (Simple & Intuitive)
The simplest approach — write the HTML to a physical file, then point Chrome to that file.
How it works:
- Generate the HTML content
- Save it to a physical file on disk
- Chrome accesses the file via
file://protocol or web URL - PDF is generated
- Optionally delete the HTML file
Example File Paths:
Physical file path: D:\www\mywebsite\pdf\invoice-inv10001.html
Web URL path: https://www.mywebsite.com/pdf/invoice-inv10001.html
File protocol: file:///D:/www/mywebsite/pdf/invoice-inv10001.htmlC# Implementation:
public class PdfGenerator
{
private static readonly string ChromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";
/// <summary>
/// Generate PDF from HTML content by saving to a temporary file
/// </summary>
/// <param name="htmlContent">The complete HTML document</param>
/// <param name="outputPdfPath">Where to save the PDF</param>
/// <param name="timeoutMs">Chrome's internal timeout in milliseconds (default: 30000)</param>
/// <returns>True if successful</returns>
public static bool GeneratePdfFromHtml(string htmlContent, string outputPdfPath, int timeoutMs = 30000)
{
// ============================================
// Step 1: Create a temporary HTML file
// ============================================
string tempFolder = Path.GetDirectoryName(outputPdfPath);
string tempHtmlPath = Path.Combine(tempFolder, $"temp-{Guid.NewGuid()}.html");
try
{
// ============================================
// Step 2: Write HTML content to the temp file
// ============================================
File.WriteAllText(tempHtmlPath, htmlContent, Encoding.UTF8);
// ============================================
// Step 3: Build Chrome command line arguments
// Using file:// protocol for local file access
// --no-pdf-header-footer: Remove date/URL from PDF
// --timeout: Max wait time for page load
// ============================================
string fileUrl = $"file:///{tempHtmlPath.Replace("\\", "/")}";
string arguments = $"--headless --disable-gpu --no-pdf-header-footer --timeout={timeoutMs} --print-to-pdf=\"{outputPdfPath}\" \"{fileUrl}\"";
// ============================================
// Step 4: Execute Chrome
// ============================================
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = ChromePath,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.Start();
// Wait for Chrome to finish
// Process timeout = Chrome timeout + 5 seconds buffer
int processTimeout = timeoutMs + 5000;
bool completed = process.WaitForExit(processTimeout);
if (!completed)
{
process.Kill();
return false;
}
return File.Exists(outputPdfPath);
}
}
finally
{
// ============================================
// Step 5: Clean up - delete the temporary HTML file
// ============================================
if (File.Exists(tempHtmlPath))
{
File.Delete(tempHtmlPath);
}
}
}
}Usage Example:
// Generate invoice HTML (with autoprint=0 to disable JavaScript print)
string htmlContent = engineInvoice.GenerateInvoice(10001, 0);
// Define output path
string pdfPath = @"D:\www\mywebsite\pdf\invoice-inv10001.pdf";
// Generate PDF
bool success = PdfGenerator.GeneratePdfFromHtml(htmlContent, pdfPath);
if (success)
{
// PDF generated successfully
TransmitPdfForDownload(pdfPath, "invoice-inv10001.pdf");
}Pros:
- Simple and straightforward
- No additional infrastructure needed
- Easy to debug (you can open the HTML file in a browser)
Cons:
- Requires disk I/O
- Need to manage temporary file cleanup
- File system permissions required
Method 2: In-Memory HTML with ConcurrentDictionary (No Physical Files)
A more elegant approach — hold the HTML in memory and serve it via an ASP.NET page. Chrome accesses the URL, receives the HTML, and generates the PDF.
How it works:
- Generate the HTML content
- Store it in a
ConcurrentDictionarywith a unique key - Chrome requests the URL with that key
- ASP.NET page retrieves HTML from dictionary and serves it
- PDF is generated
- HTML is removed from memory
Why ConcurrentDictionary?
- Thread-safe for concurrent access
- Fast in-memory storage
- No disk I/O overhead
- Automatic cleanup after use
Note: This approach works when your website runs in a single worker process. For web farms or multiple worker processes, use Method 1 or a distributed cache.
Step 1: Create a Static Dictionary to Hold HTML Content
// Global.asax.cs or a separate static class
using System.Collections.Concurrent;
public static class HtmlMemoryStore
{
// ============================================
// Thread-safe dictionary to store HTML content
// Key: unique identifier (e.g., invoice_id or GUID)
// Value: the HTML string
// ============================================
public static ConcurrentDictionary<string, string> HtmlContent = new ConcurrentDictionary<string, string>();
/// <summary>
/// Store HTML content and return a unique key
/// </summary>
public static string Store(string html)
{
// Generate a unique key using GUID for security
string key = Guid.NewGuid().ToString("N");
// Store the HTML
HtmlContent[key] = html;
return key;
}
/// <summary>
/// Retrieve and remove HTML content (one-time use)
/// </summary>
public static string RetrieveAndRemove(string key)
{
// Try to remove and return the HTML
// This ensures the HTML can only be retrieved once
if (HtmlContent.TryRemove(key, out string html))
{
return html;
}
return null;
}
}Step 2: Create the HTML Serving Page
Create a blank ASPX page that serves the HTML content.
pdfHtmlSource.aspx (Frontend – just the page directive):
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="pdfHtmlSource.aspx.cs" Inherits="myweb.pdfHtmlSource" %>pdfHtmlSource.aspx.cs (Backend):
using System;
using System.Web;
public partial class pdfHtmlSource : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// ============================================
// Security: Only allow local requests
// Chrome.exe will be making requests from the same server
// This prevents external access to the HTML content
// ============================================
if (!Request.IsLocal)
{
// Reject non-local connections
Response.StatusCode = 403; // Forbidden
Response.Write("Access denied");
return;
}
// ============================================
// Get the key from query string
// Note: We can only use GET requests here
// Chrome.exe makes simple URL GET requests
// Form POST or JSON body won't work
// ============================================
string key = Request["key"];
if (string.IsNullOrEmpty(key))
{
Response.StatusCode = 400; // Bad Request
Response.Write("Missing key parameter");
return;
}
// ============================================
// Retrieve the HTML content from memory
// This also removes it from the dictionary (one-time use)
// ============================================
string html = HtmlMemoryStore.RetrieveAndRemove(key);
if (string.IsNullOrEmpty(html))
{
// HTML not found or already retrieved
Response.StatusCode = 404; // Not Found
Response.Write("Content not found or expired");
return;
}
// ============================================
// Output the HTML content
// ============================================
Response.ContentType = "text/html";
Response.Write(html);
}
}Step 3: Generate PDF Using the In-Memory Approach
public class PdfGenerator
{
private static readonly string ChromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";
/// <summary>
/// Generate PDF from HTML using in-memory storage
/// </summary>
/// <param name="htmlContent">The complete HTML document</param>
/// <param name="outputPdfPath">Where to save the PDF</param>
/// <param name="timeoutMs">Chrome's internal timeout in milliseconds (default: 30000)</param>
/// <returns>True if successful</returns>
public static bool GeneratePdfFromMemory(string htmlContent, string outputPdfPath, int timeoutMs = 30000)
{
// ============================================
// Step 1: Store the HTML in memory and get a unique key
// ============================================
string key = HtmlMemoryStore.Store(htmlContent);
try
{
// ============================================
// Step 2: Build the URL for Chrome to access
// Using localhost for security (Request.IsLocal check)
// ============================================
string sourceUrl = $"http://localhost/pdfHtmlSource.aspx?key={key}";
// Or with a specific port if your site uses one:
// string sourceUrl = $"http://localhost:8080/pdfHtmlSource.aspx?key={key}";
// Or using 127.0.0.1:
// string sourceUrl = $"http://127.0.0.1/pdfHtmlSource.aspx?key={key}";
// ============================================
// Step 3: Build Chrome command line arguments
// --no-pdf-header-footer: Remove date/URL from PDF
// --timeout: Max wait time for page load
// ============================================
string arguments = $"--headless --disable-gpu --no-pdf-header-footer --timeout={timeoutMs} --print-to-pdf=\"{outputPdfPath}\" \"{sourceUrl}\"";
// ============================================
// Step 4: Execute Chrome
// ============================================
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = ChromePath,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.Start();
// Wait for Chrome to finish
// Process timeout = Chrome timeout + 5 seconds buffer
int processTimeout = timeoutMs + 5000;
bool completed = process.WaitForExit(processTimeout);
if (!completed)
{
process.Kill();
return false;
}
return File.Exists(outputPdfPath);
}
}
finally
{
// ============================================
// Step 5: Cleanup - ensure HTML is removed from memory
// (It should already be removed by RetrieveAndRemove,
// but this is a safety net in case Chrome failed to access it)
// ============================================
HtmlMemoryStore.HtmlContent.TryRemove(key, out _);
}
}
}Complete Example – Invoice PDF Generation:
// apiInvoicePdf.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
// ============================================
// Authentication
// ============================================
if (Session["login_user"] == null)
{
Response.StatusCode = 401;
return;
}
// ============================================
// Get invoice ID
// ============================================
int invoice_id = 0;
if (!int.TryParse(Request["id"], out invoice_id) || invoice_id <= 0)
{
Response.StatusCode = 400;
return;
}
// ============================================
// Generate the invoice HTML
// autoprint = 0 to disable JavaScript print function
// (Even with autoprint=1, Chrome headless ignores JavaScript,
// but it's cleaner to not include it)
// ============================================
string htmlContent = engineInvoice.GenerateInvoice(invoice_id, 0);
if (string.IsNullOrEmpty(htmlContent))
{
Response.StatusCode = 404;
Response.Write("Invoice not found");
return;
}
// ============================================
// Define PDF output path
// ============================================
string pdfFolder = Server.MapPath("~/pdf/");
string pdfFileName = $"invoice-{invoice_id}.pdf";
string pdfPath = Path.Combine(pdfFolder, pdfFileName);
// Ensure folder exists
if (!Directory.Exists(pdfFolder))
{
Directory.CreateDirectory(pdfFolder);
}
// ============================================
// Generate PDF using in-memory method
// ============================================
bool success = PdfGenerator.GeneratePdfFromMemory(htmlContent, pdfPath);
if (!success)
{
Response.StatusCode = 500;
Response.Write("PDF generation failed");
return;
}
// ============================================
// Send PDF for download
// ============================================
TransmitPdfForDownload(pdfPath, pdfFileName);
// ============================================
// Optionally delete the PDF after sending
// ============================================
// File.Delete(pdfPath);
}The Chrome Command Line (for reference):
"C:\Program Files\Google\Chrome\Application\chrome.exe" --headless --disable-gpu --no-pdf-header-footer --timeout=30000 --print-to-pdf="D:\www\mywebsite\pdf\invoice-inv10001.pdf" "http://localhost/pdfHtmlSource.aspx?key=a1b2c3d4e5f6"Pros:
- No temporary HTML files on disk
- Faster (no disk I/O for HTML)
- Cleaner architecture
- One-time use security (HTML removed after retrieval)
Cons:
- Requires single worker process (or distributed cache for web farms)
- HTML stored in memory (watch for memory usage with large documents)
- Slightly more complex setup
Method Comparison
| Aspect | Method 1: Physical File | Method 2: In-Memory |
|---|---|---|
| Disk I/O | Yes (write HTML, read HTML) | No (memory only) |
| Temporary Files | Yes (need cleanup) | No |
| Speed | Slower | Faster |
| Memory Usage | Lower | Higher (HTML in memory) |
| Complexity | Simple | Moderate |
| Multi-Server Support | Yes (files accessible across instances) | Single process only* |
| Security | File permissions | localhost-only access |
*For multi-server setups with Method 2, use a distributed cache like Redis instead of ConcurrentDictionary.
Summary
In this part, we covered:
- Chrome Headless PDF Generation — Using Chrome’s
--print-to-pdfcommand line feature - Command Line Parameters — Understanding
--headless,--disable-gpu,--no-pdf-header-footer,--timeout - Chrome Headless Changes — Since Chrome 112, headless mode is unified with regular Chrome
- Method 1: Physical Files — Simple approach using temporary HTML files
- Method 2: In-Memory — Using
ConcurrentDictionaryand a dedicated ASPX page - Security — Using
Request.IsLocalto restrict access - Download Delivery — Sending PDF to browser for download
Key Points:
- Chrome.exe requires server access (dedicated server, VPS, or on-premise)
- Does not work on shared hosting
- Produces pixel-perfect PDFs using Chrome’s rendering engine
- JavaScript is ignored by Chrome headless (auto-print functions won’t run)
- Use
--no-pdf-header-footer(new) instead of--print-to-pdf-no-header(legacy) for Chrome 120+ - Use
--timeoutto control maximum page load time
In Part 4.2, we’ll cover Puppeteer Sharp — a more flexible solution that works in more hosting environments and provides greater programmatic control.
