tempest/responsive-image
Generate responsive images in PHP and render the correct HTML. Define your source and public paths, create a factory, then output an img tag with a ready-made srcset of scaled sizes. Early work in progress, so expect changes.
Architecture fit. tempest/responsive-image is a thin, opinionated layer that does two things: (1) generates downscaled derivatives from a source image at a configurable set of widths, and (2) renders an <img> tag with a srcset/sizes pair. Architecturally it is a rendering-time convenience, not an image pipeline: it expects a source directory (srcPath), a public output directory (publicPath), and produces files named by dimensions (parrot-1920-1280.jpg). That is a good fit for a statically deployed, low-traffic PHP app where images are curated assets committed to the repo or uploaded via an admin panel. It is a poor fit for user-generated content at scale, multi-tenant asset storage on object stores, or anything requiring signed URLs, on-the-fly transformation, or CDN edge negotiation. Note the packaging reality: this is a Tempest package, not a Laravel one. The quickstart instantiates ResponsiveImageFactory and ResponsiveImageConfig directly, which is the most promising integration surface — but we must verify the factory does not reach into Tempest's container, discovery, or config bootstrapping, because if it does we cannot use it inside a Laravel service container without a shim.
Integration feasibility. Two viable integration shapes exist. Shape A (preferred): treat the two classes as a plain-PHP library, bind ResponsiveImageConfig as a Laravel singleton from config/images.php, inject ResponsiveImageFactory into a Blade directive or a @responsiveImage($path) helper, and wrap generation in an artisan command so it runs at deploy time rather than per-request. This is achievable in a day or two of spike work. Shape B: install the full Tempest framework alongside Laravel — do not do this. Composer-wise the package is tempest/responsive-image and will pull Tempest support packages as transitive dependencies; the spike's first deliverable is a composer why on the dependency tree to confirm what actually gets dragged in. If the transitive footprint is only small utility packages (path helpers, string helpers), Shape A is safe. If it requires tempest/framework, the integration cost triples and the evaluation should end there.
Technical risk. Risks, in descending order of severity:
create() and echoes html — if derivative generation happens inline, the first request for each image blocks on GD/Imagick work, and concurrent first-hits will stampede. This must be pre-generated at build/deploy time or lazily behind a queue.publicPath with dimension-encoded filenames. There is no visible cache-busting hash, no manifest, and no deletion story. Changing the source image at the same path will serve stale derivatives forever; changing the configured widths orphans files on disk with no GC.<picture>/type negotiation, no loading="lazy", no decoding, no explicit dimensions (CLS risk), no fetchpriority. For a modern Core Web Vitals budget this is table stakes; we would need to extend the renderer regardless.Key questions for the maintainer / spike.
ResponsiveImageFactory require the Tempest container, or is it constructible with only ResponsiveImageConfig in a bare PHP process?tempest/framework?<picture> support on the roadmap, or is JPEG-only the intended scope?<img>?Stack fit. The package fits a Laravel monolith whose images are a bounded, curated set — marketing pages, editorial content, a product catalogue measured in thousands rather than millions — served from local disk or a single bucket mounted into publicPath. It does not fit a multi-tenant SaaS where tenants upload arbitrary images, nor a headless/API-only backend fronted by a SPA (though the generated srcset string is still useful there). Before committing, benchmark it against the incumbent options on the same page: spatie/laravel-medialibrary + intervention/image (more capable, far healthier, heavier), and a managed transform CDN such as Cloudflare Images, Imgix, or Cloudinary (zero maintenance, per-image cost). The honest framing for stakeholders: this package saves perhaps 150–300 lines of glue code over hand-rolling srcset generation around intervention/image, and in exchange you accept single-maintainer risk. The buy case is thin; the build case is also thin; the CDN case is usually the strongest at any real scale.
Migration path. Treat this as an additive, strangler-style rollout, never a big-bang.
ImageRenderer service (and a @responsiveImage Blade directive) that delegates to the package. Every template calls our wrapper, never ResponsiveImageFactory directly. This is the seam that makes swapping to a CDN or a fork a one-file change.images:generate artisan command invoked in the deploy pipeline (or a container build step), writing into a gitignored public/images/generated path. Requests never trigger generation. Emit a manifest (path → hashed filenames) so templates can cache-bust.Compatibility. Pin the version exactly ("tempest/responsive-image": "0.x.y" — no caret) given WIP status; a 0.x minor bump is a de facto breaking change. Verify PHP version and Laravel support matrix explicitly; confirm the image driver is present in every environment (GD is near-universal, Imagick often absent from slim containers, vips almost never pre-installed). Ensure local, CI, and production share the same driver and PHP version so derivative output is byte-identical — otherwise deploy-time generation and local development diverge. Confirm the package honours Laravel's public_path() and filesystem abstraction; if it writes to a raw absolute path, wrap it so a move to S3 later does not require touching call sites.
Sequencing. Spike and decision gate → wrapper + config binding + artisan command → CI step asserting generation is deterministic and idempotent → single-surface pilot → measurements → broad rollout → fork-or-replace decision reviewed at the six-month mark. Do not begin until the wrapper exists; the wrapper is the entire risk-mitigation strategy.
Maintenance. This is a fork-and-own dependency, and the operating assumption must be that upstream goes quiet. Budget roughly 10–15% of one engineer's time ongoing: tracking the upstream repo, rebasing a small internal patch set (WebP/AVIF output, loading/decoding/width/height attributes, content-hashed filenames, prune command), and re-verifying on Laravel and PHP upgrades. Assign a named owner. If nobody will own it, do not adopt it — that is the whole decision.
Support. No vendor, no SLA, no security response process. MIT gives you the legal right to fork, which is the only real support guarantee. Security exposure is modest (image decoding is the main attack surface, and that risk is inherited from GD/Imagick, not this package), but a malicious upload path must be validated upstream of this library regardless of which option you choose.
Scaling. Generation cost is CPU-bound and linear in (number of images × widths in the ladder). That is entirely acceptable at deploy time and entirely unacceptable per-request. Storage is source count × ladder size, typically 4–6× the original bytes — trivial for thousands of images, a real line item for millions. Serving scales fine because the output is static files behind a CDN; the package is not in the hot path once generation is pre-baked. The scaling ceiling is reached when image count makes full re-generation at deploy too slow, at which point you need incremental generation driven by a manifest diff — a feature the package does not appear to have, so it becomes internal work. Beyond that ceiling, migrate to a transform CDN; the wrapper seam makes that a contained change.
Failure modes. Enumerate and instrument each:
<img> in production.width/height emitted; CLS regressions go unnoticed unless measured. Add attribute injection in the wrapper and assert in the pilot.Ramp-up. Low for a mid-level Laravel engineer — the API is two classes and a config object, and a competent developer is productive in under a day. The real ramp-up is operational: whoever owns deploys must understand the generate-prune-manifest cycle, and whoever owns templates must use the wrapper rather than the raw factory. Write a two-page runbook covering the deploy step, the prune command, the driver-pinning rule, and the escalation path for a broken image. Total onboarding cost, including the spike, is on the order of one engineer-week — which is the correct yardstick against the alternative of a CDN integration (also roughly one week, but with recurring cost and near-zero ongoing engineering). For a small, curated image set on a modest Laravel app with an engineer willing to own a fork, this package is a defensible, cheap choice; for anything larger or less well-staffed, budget the week toward a managed transform CDN instead.
How can I help you explore Laravel packages today?