Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Browsershot Laravel Package

spatie/browsershot

Browsershot renders web pages to images or PDFs using Puppeteer and headless Chrome. Accept HTML, local files, or URLs, run JavaScript, capture body HTML and triggered requests, and customize headless behavior from PHP.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit Browsershot is a thin PHP façade over a Node/Puppeteer subprocess. There is no PHP-native rendering engine, no server, and no persistent daemon in the default mode: each call shells out, starts Chrome, renders, writes a file, and exits. That makes it an external process dependency inside your Laravel app, not a library in the conventional sense. Fit is excellent where the app already owns the rendering surface — Blade views, authenticated HTML, or internal URLs — and where output is a document artifact (invoice, statement, report, certificate, audit snapshot). Fit is poor for high-volume, low-latency, or sandboxed-hostile environments.

Integration feasibility Composer-installable, MIT, actively maintained by Spatie, v5.x line with a documented changelog cadence and roughly weekly patch activity. The API is fluent and small: Browsershot::url(), ->html(), ->htmlFromFilePath(), ->save($path), plus bodyHtml() and triggeredRequests() for post-JS inspection. Extension points matter for us: evaluateOnNewDocument, request interception (now only engaged when needed as of 5.3.0), setNodeEnv, disableRedirects, newHeadless, media features (v5), and remote connection support (throwOnRemoteConnectionError in 5.1.0) for a hosted Chrome endpoint. Real cost is not PHP — it is provisioning Node ≥ 23-compatible Puppeteer, a Chrome/chrome-headless-shell binary, and poppler-utils for the test path.

Technical risk

  • Host provisioning is the primary risk. Node, Puppeteer, Chrome, shared libraries, and fonts must exist in every environment: local, CI, staging, production, and each container image. CI images commonly need AppArmor/sandbox adjustments (see 5.0.9).
  • Security surface. Rendering arbitrary URLs means SSRF. The project has patched local-file read bypasses (5.2.1: UNC paths, protocol-relative URLs; 4.3.1: file:/ checks), which tells us this class of bug recurs. Never pass user-controlled URLs without an allowlist and egress controls.
  • Version coupling. Major versions track Puppeteer majors (v5 requires Puppeteer ≥ 23). A Puppeteer bump can be a forced upgrade; the chrome-headless-shell legacy-mode support added in 5.0.8 is a compatibility escape hatch, not a strategy.
  • Metadata caveats. "Dependents: 0" is almost certainly a Packagist aggregation artifact, not reality — do not use it as an adoption signal. The listed release date (2026-10-26) is future-dated; verify the actual latest tag before pinning.
  • Opportunity score 44.55 against a composite 69.46 suggests a mature, well-documented, but saturated capability — little differentiation to be gained from the package itself; value is entirely in how we wire it.

Key questions

  1. Is our deploy target able to ship a Node + Chrome runtime, or do we need remote/containerized rendering (Browserless, Lambda, sidecar)?
  2. Do we render only internal/owned templates, or must we fetch third-party URLs? If third-party, what is the egress policy?
  3. What is peak documents-per-minute, and does that fit a per-call subprocess model or require a warm browser pool?
  4. Are outputs legally or financially material (invoices, statements)? If so, what is the archival and reproducibility requirement?
  5. Who owns Chrome/Puppeteer CVE patching, and on what cadence?

Integration Approach

Stack fit Native Laravel/Lumen/PHP 8.x with Symfony 8 support as of 5.1.1. No framework lock-in beyond Composer. It slots cleanly behind a queued job: a controller or event dispatches GeneratePdfJob, the job builds a Blade view to HTML (or a signed temporary URL), calls Browsershot, writes to Storage::disk(), and records the artifact path. Rendering is synchronous and blocking within a worker, so it must never run in a web request path.

Migration path Low switching cost in either direction, which is the real strategic value. Because the public API is html|url → save, we can wrap it behind our own DocumentRenderer interface on day one and swap to wkhtmltopdf, Dompdf, Gotenberg, or a hosted rendering API without touching call sites. If v2-style Chrome CLI or an existing Dompdf path is in place, the migration is mechanical: same inputs, better CSS/JS fidelity, one new runtime dependency. Do not let Browsershot:: calls leak into controllers or Blade.

Compatibility

  • PHP: modern 8.x; Symfony 8 supported.
  • Node/Puppeteer: v5 requires Puppeteer ≥ 23; chrome-headless-shell supported for legacy headless.
  • Browser features: full modern CSS (grid, flex, custom properties), web fonts, and JS execution — this is the reason to choose it over Dompdf, and the reason output can shift when Chrome updates.
  • Testing: requires pdftotext/poppler locally and in CI; plan for that in the pipeline image, not as an afterthought.

Sequencing

  1. Spike (1 sprint): render one real invoice and one real report from Blade; measure cold/warm latency, memory, and output fidelity in the target container image.
  2. Abstraction: define DocumentRenderer contract + BrowsershotRenderer implementation; add config for binary paths, Node path, timeout, and remote endpoint.
  3. Runtime hardening: build a pinned container layer (Node version, Puppeteer version, Chrome version, font set) and publish it as the only supported render environment.
  4. Security controls: URL allowlist, block file:// and internal metadata IPs, disable redirects where feasible, cap timeouts and payload sizes.
  5. Queue integration: dedicated queue and worker pool, retries with backoff, idempotent artifact keys, failure alerting.
  6. Observability: per-render timing, success rate, output size, and a canary document compared against a stored baseline after every Chrome/Puppeteer bump.
  7. Scale path (only if needed): move to remote connection / shared browser service.

Operational Impact

Maintenance Two version streams to track: the Composer package and the Node/Puppeteer/Chrome trio. The package is well-maintained (frequent patches, Dependabot-driven hygiene, Symfony 8 and PHP 8.5 test coverage), but its updates are the smaller half of the burden. Assume quarterly Chrome upgrades minimum, each requiring a golden-document regression check because PDF output is not guaranteed byte-stable across Chrome versions. Pin exact versions in the image; never float.

Support Spatie offers documentation, a changelog, and a security mailbox, but no commercial SLA for the package. Internal ownership must be explicit: one team owns the render image and the upgrade cadence. Expect to debug at the Node/Chrome layer (missing shared libs, font substitution, font hinting, sandbox flags, locale/timezone differences) rather than in PHP. Timezone and locale set in PHP do not automatically propagate to Chrome — set them explicitly via Node env.

Scaling Per-call subprocess spawning is the cost model: typical renders land in the low hundreds of milliseconds to a few seconds, dominated by Chrome startup. This does not scale horizontally within a single worker — each render needs its own headroom. Practical guidance:

  • Dedicated queue + worker pool, isolated from latency-sensitive jobs.
  • Concurrency capped per host (CPU-bound; roughly one render per core with margin).
  • Hard timeouts on every call; default timeout behavior must be configured, not assumed.
  • Memory: Chrome is the largest resident cost; size containers from measured peak, not from PHP-FPM norms.
  • Above a few documents/second sustained, move to a warm browser pool or remote rendering endpoint — the package supports remote connections for exactly this.

Failure modes

Failure Symptom Mitigation
Node/Puppeteer missing or version-mismatched Immediate exception on every render Pinned image; startup smoke test
Chrome sandbox/permissions (containers, AppArmor) Silent hang or crash Documented sandbox flags; verify in CI
Render timeout on heavy pages Job failure, orphaned Chrome process Explicit timeout, process reaping, retry with backoff
SSRF via user-supplied URL Internal network access, data exposure Allowlist, redirect blocking, egress firewall
Local file read (file://, UNC, protocol-relative) Host filesystem disclosure Stay on ≥ 5.2.1; validate input, never pass raw user HTML/URLs
Missing fonts / substituted fonts Visibly wrong documents in production only Bake font set into image; golden-document diff
Output drift after Chrome upgrade Layout regressions on legally material docs Canary render + baseline diff in release gate
Queue saturation Backlog, stale documents Dedicated queue, priority lanes, backlog alerting

Ramp-up A competent Laravel engineer is productive in under a day at the API level — the fluent interface is genuinely simple. Operational competency takes considerably longer: the team must learn Chrome-in-container debugging, process lifecycle, and font/metrics behavior. Budget roughly one sprint end-to-end for a first production-quality integration including image, queue, and observability, and treat the render image as a maintained product artifact rather than a build detail.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core