Artisan in production
At a glance
Section titled “At a glance”In production, inject a configured renderer and a PHP Standards Recommendation 3 (PSR-3) logger, reuse the live Chrome process between renders, set explicit heights for multi-element documents, and limit the render path with an upstream timeout.
Conceptual overview
Section titled “Conceptual overview”BrowserPool keeps one Chrome process alive (keepAlive: true) and restarts it every 100 renders to limit memory growth, a known accumulation pattern in long-lived Chrome DevTools Protocol (CDP) clients. For a worker that renders many documents, use one long-lived renderer instead of one renderer per request, so you rarely pay the Chrome startup cost.
Code sample — Production
Section titled “Code sample — Production”<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;use NextPDF\Artisan\ChromeRendererConfig;use NextPDF\Artisan\Exception\ChromeNotAvailableException;use NextPDF\Artisan\Exception\ChromeRenderException;use Psr\Log\LoggerInterface;
final class ReportRenderer{ private ChromeHtmlRenderer $renderer;
public function __construct(LoggerInterface $logger) { $config = ChromeRendererConfig::fromArray([ 'chrome_binary' => getenv('CHROME_BINARY') ?: null, 'render_timeout' => 45, 'max_html_size' => 2_000_000, 'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'), ]);
$this->renderer = new ChromeHtmlRenderer($config, $logger); }
public function render(string $html, float $widthPt, float $heightPt = 0.0): string { try { return $this->renderer->render($html, $widthPt, $heightPt)->getPdfData(); } catch (ChromeNotAvailableException $e) { // Deployment fault: Chrome runtime missing. Page the on-call owner. throw $e; } catch (ChromeRenderException $e) { // Render-time fault: timeout, crash, empty output. Retryable once. throw $e; } }
public function shutdown(): void { $this->renderer->close(); }}Construct the renderer once, then reuse it. Call close() when the worker shuts down to release the Chrome process deterministically instead of waiting for the destructor. The two catch arms separate a deployment fault (missing runtime) from a render-time fault (retryable). Do not use empty catch blocks.
Wire it into a container as a singleton:
$container->singleton(ReportRenderer::class, fn ($c) => new ReportRenderer($c->get(Psr\Log\LoggerInterface::class)));Height handling
Section titled “Height handling”When you omit height, the bridge measures content height in Chrome (max of body/document scroll and offset heights), converts it to points, and adds a ~0.2 inch (~14.4 pt) safety buffer. The buffer covers the gap between Chrome’s viewport layout and its print-layout reflow. Without it, printToPDF can spill content onto a second page that PageImporter (page 0 only) would clip. The bridge enforces a minimum paper height of 0.1 inch. The tests ChromeHtmlRendererTest::renderUsesAutoFitHeightByDefault, ::renderAutoFitBufferIsAddedNotSubtracted, and ::renderAppliesMinimumHeightOf0Point1InchForTinyExplicitHeight assert this behavior.
For fixed-layout documents (invoices, certificates), pass an explicit height in points. When height is explicit, no buffer is added, and the output matches the requested paper size exactly (asserted by ::renderHonorsExplicitHeightWithoutAutoBuffer).
Batch workers
Section titled “Batch workers”- Construct one renderer per worker, and reuse it.
BrowserPoolreuses the live browser and automatically restarts on the 100-render boundary. - Call
close()at worker shutdown and between large batches when you want a fresh Chrome process sooner than the 100-render boundary. - The destructor calls
close(), but an explicitclose()is deterministic and preferred in long-running processes. - Restart notices are logged at
noticelevel with the render count; alert on an elevated restart rate because it indicates heavier-than-expected documents.
Observability
Section titled “Observability”Inject a PSR-3 logger. The renderer emits these events and levels:
| Event | Level | Context |
|---|---|---|
| Render start | debug | size, width, height |
| Render complete | debug | pdfSize, contentHeight |
| Browser launch | info | binary |
| Browser restart | notice | count |
| Browser close | debug | renderCount |
No HTML, PDF bytes, or extracted text is logged. This keeps payloads out of operational logs and aligns with National Institute of Standards and Technology Special Publication (NIST SP) 800-92 log-content guidance. Build latency service-level objectives (SLOs) from the start/complete pair, and build a restart-rate alert from the notice events.
Deployment patterns
Section titled “Deployment patterns”- Sidecar Chrome: run Chrome in the same container as the PHP worker; pin
chrome_binary. Provision a sandbox-capable container; see /integrations/artisan/chrome-renderer-setup/. - Containerless / CLI: Artisan has no dependency injection container. Use
EInvoiceServiceFactoryfor Premium e-invoice contracts in command-line interface (CLI) runners; see /integrations/artisan/boot-and-discovery/. - Resource bounding: pair
render_timeoutwith an upstream request budget and a host cgroup/ulimit. See the threat model on /integrations/artisan/security-and-operations/.
Edge cases & gotchas
Section titled “Edge cases & gotchas”- A renderer caught mid-render still closes the Chrome page (
finally), and the pool stays usable. - Reusing one renderer across threads/processes is not supported; one renderer owns one Chrome process.
- The 100-render restart is fixed; size batches with it in mind so latency spikes stay predictable.
Performance
Section titled “Performance”Steady-state cost is Chrome layout of the input plus printToPDF, not bridge overhead. keepAlive spreads startup cost across renders. Expect a latency spike on every 100th render (process restart); surface it in SLOs rather than treating it as an incident.
Security notes
Section titled “Security notes”Production paths receive untrusted HTML. Re-read /integrations/artisan/security-and-operations/. The network barriers hold regardless of configuration, but no_sandbox: true removes Chrome process isolation and raises the trust requirement on the input.
Commercial context
Section titled “Commercial context”In containerless workers, EInvoiceServiceFactory returns null when Premium is not installed, so the open-source render path continues unchanged. Install Pro/Enterprise to enable e-invoice embedding and validation on the rendered document.
See also
Section titled “See also”- /integrations/artisan/quickstart/
- /integrations/artisan/configuration/
- /integrations/artisan/security-and-operations/
- /integrations/artisan/chrome-renderer-setup/
- /integrations/artisan/troubleshooting/