<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fomin Vasyl</title>
    <description>The latest articles on DEV Community by Fomin Vasyl (@fomvasss).</description>
    <link>https://dev.to/fomvasss</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F227498%2F7f8b063e-79b6-4244-8897-5a9aa8d644d3.png</url>
      <title>DEV Community: Fomin Vasyl</title>
      <link>https://dev.to/fomvasss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fomvasss"/>
    <language>en</language>
    <item>
      <title>Stop Pre-Generating Image Thumbnails in Laravel — Do It On-The-Fly Instead</title>
      <dc:creator>Fomin Vasyl</dc:creator>
      <pubDate>Thu, 07 May 2026 20:21:26 +0000</pubDate>
      <link>https://dev.to/fomvasss/stop-pre-generating-image-thumbnails-in-laravel-do-it-on-the-fly-instead-3lb8</link>
      <guid>https://dev.to/fomvasss/stop-pre-generating-image-thumbnails-in-laravel-do-it-on-the-fly-instead-3lb8</guid>
      <description>&lt;p&gt;Every Laravel project eventually hits the same wall: a designer asks for a new image size, and suddenly you're writing a migration, a queue job, and a Media Library conversion — just to serve a 400px thumbnail.&lt;/p&gt;

&lt;p&gt;There's a simpler way. Generate image variants on demand, cache the result permanently, and move on.&lt;/p&gt;

&lt;p&gt;That's exactly what &lt;a href="https://github.com/fomvasss/laravel-imagepresets" rel="noopener noreferrer"&gt;laravel-imagepresets&lt;/a&gt; does.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Pre-Generated Thumbnails
&lt;/h2&gt;

&lt;p&gt;When you pre-generate image variants (the approach Spatie Media Library encourages by default), you pay upfront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage costs for every variant of every image, even ones never viewed&lt;/li&gt;
&lt;li&gt;Queue processing time on upload&lt;/li&gt;
&lt;li&gt;Pain when design requirements change and you need to regenerate thousands of files&lt;/li&gt;
&lt;li&gt;Complex seeding/migration logic for existing images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On-the-fly processing flips this: you process an image &lt;strong&gt;the first time it's requested&lt;/strong&gt;, cache the result, and never touch it again. The tradeoff is a slightly slower first request — which is usually invisible to users.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is laravel-imagepresets?
&lt;/h2&gt;

&lt;p&gt;It's a Laravel package built on &lt;a href="https://glide.thephpleague.com/" rel="noopener noreferrer"&gt;League/Glide&lt;/a&gt; that gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single &lt;code&gt;/imagepreset&lt;/code&gt; route that handles all image transformations&lt;/li&gt;
&lt;li&gt;Automatic caching to any Laravel filesystem disk (local, S3, GCS, FTP)&lt;/li&gt;
&lt;li&gt;A clean API — helper function, Facade, and Blade directive&lt;/li&gt;
&lt;li&gt;Named presets so you define sizes once and reuse them everywhere&lt;/li&gt;
&lt;li&gt;Production-ready security: SSRF protection, allowlists, signed URLs, SVG sanitization&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require fomvasss/laravel-imagepresets
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;imagepresets-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service provider is auto-discovered. No manual registration needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your First Image URL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Resize to 800px wide, convert to WebP&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;imagepreset_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'storage/images/photo.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'webp'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// → https://example.com/imagepreset?fm=webp&amp;amp;src=storage%2Fimages%2Fphoto.jpg&amp;amp;w=800&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first hit, Glide resizes and converts the image, stores it on your configured disk, and returns it with a one-year &lt;code&gt;Cache-Control&lt;/code&gt; header. The next request? Pure cache — Laravel never runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Named Presets: Define Once, Use Everywhere
&lt;/h2&gt;

&lt;p&gt;Hardcoding &lt;code&gt;['w' =&amp;gt; 300, 'h' =&amp;gt; 200, 'fm' =&amp;gt; 'webp', 'fit' =&amp;gt; 'crop']&lt;/code&gt; everywhere is a maintenance headache. Named presets solve this.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;config/imagepresets.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'presets'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'thumb'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'webp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fit'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'crop'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'hero'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'webp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'q'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'avatar'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s1"&gt;'h'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'webp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fit'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'crop'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'og_banner'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;650&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fit'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'fill-max'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bg'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ffffff'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// String shorthand&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;imagepreset_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'photo.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'thumb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Facade&lt;/span&gt;
&lt;span class="nc"&gt;Imagepreset&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'photo.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hero'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{-- Blade directive --}}
&amp;lt;img src="@imagepreset('photo.jpg', 'thumb')" alt="Thumbnail"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need to override one param? Pass it alongside the preset name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use thumb preset but output JPG instead of WebP&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;imagepreset_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'photo.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'preset'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'thumb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jpg'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fit Methods Explained
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;fit&lt;/code&gt; parameter controls how the image fills the target dimensions. Choosing the wrong one is a common source of stretched or oddly-cropped images.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fit&lt;/th&gt;
&lt;th&gt;Use when...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You need exact pixel dimensions (cards, avatars). Edges may be trimmed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;contain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The full image must be visible. No fill — transparent space is left.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full image visible, remaining canvas filled with &lt;code&gt;bg&lt;/code&gt; color. May upscale.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill-max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Like &lt;code&gt;fill&lt;/code&gt; but never upscales. Great for OG images and social banners.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Like &lt;code&gt;contain&lt;/code&gt; but never upscales beyond original size.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stretch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forces exact dimensions ignoring aspect ratio. Rarely a good idea.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For OG images, &lt;code&gt;fill-max&lt;/code&gt; is your friend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;imagepreset_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'post-image.jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'w'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'h'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;650&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'fit'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'fill-max'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'fm'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'bg'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'ffffff'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  S3 / Remote Disk Support
&lt;/h2&gt;

&lt;p&gt;Just set the disk in your &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMAGEPRESET_DISK=s3
IMAGEPRESET_PATH=imagepresets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package detects remote disks automatically. Glide processes the image locally, uploads the result to S3 via Flysystem, deletes the local temp file, and streams the response directly from S3. No extra code needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Out of the Box
&lt;/h2&gt;

&lt;p&gt;Open image-resizing endpoints are a common attack surface. The package handles the main threats:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Allowlists&lt;/strong&gt; prevent arbitrary dimensions from being requested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'allowed_widths'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="s1"&gt;'allowed_heights'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="s1"&gt;'allowed_formats'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'webp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jpg'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'png'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SSRF protection&lt;/strong&gt; blocks remote image sources that point to private IPs or localhost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image bomb protection&lt;/strong&gt; rejects files that exceed &lt;code&gt;max_image_pixels&lt;/code&gt; (default: 150 Mpx).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signed URLs&lt;/strong&gt; (optional) make it impossible to tamper with parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMAGEPRESET_SIGNED_URL=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once enabled, &lt;code&gt;imagepreset_url()&lt;/code&gt; automatically generates HMAC-signed URLs. Changing any parameter returns 403.&lt;/p&gt;




&lt;h2&gt;
  
  
  Race Condition Protection
&lt;/h2&gt;

&lt;p&gt;What happens when 50 users simultaneously request the same uncached image? Without protection, you'd process the same image 50 times.&lt;/p&gt;

&lt;p&gt;The package uses &lt;code&gt;Cache::lock()&lt;/code&gt; to ensure only one process generates each variant. Set &lt;code&gt;CACHE_DRIVER=redis&lt;/code&gt; for this to work correctly across multiple servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit Log: Discover What Your Frontend Actually Needs
&lt;/h2&gt;

&lt;p&gt;Before locking down allowlists in production, you can enable audit logging in development to see exactly which params your frontend requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMAGEPRESET_AUDIT_LOG=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then extract unique values from your logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oh&lt;/span&gt; &lt;span class="s1"&gt;'"w":[0-9]*'&lt;/span&gt; storage/logs/&lt;span class="k"&gt;*&lt;/span&gt;.log | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the findings to populate your allowlists before deploying.&lt;/p&gt;




&lt;h2&gt;
  
  
  CDN and Nginx Caching
&lt;/h2&gt;

&lt;p&gt;Every response ships with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable
ETag: "&amp;lt;hash&amp;gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it trivial to cache at the edge. The README includes ready-to-use configs for Nginx proxy cache and Cloudflare Cache Rules.&lt;/p&gt;

&lt;p&gt;To verify your cache is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run twice — first should be MISS, second HIT&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-D&lt;/span&gt; - &lt;span class="s2"&gt;"https://example.com/imagepreset?src=photo.jpg&amp;amp;w=800&amp;amp;fm=webp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Clearing the Cache
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clear all cached presets&lt;/span&gt;
php artisan imagepresets:clear

&lt;span class="c"&gt;# Clear a specific remote disk&lt;/span&gt;
php artisan imagepresets:clear &lt;span class="nt"&gt;--disk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;s3 &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;imagepresets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dependency&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;^8.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laravel&lt;/td&gt;
&lt;td&gt;10 / 11 / 12 / 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;league/glide&lt;/td&gt;
&lt;td&gt;^2.0 | ^3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Optional: &lt;code&gt;imagick&lt;/code&gt; extension for AVIF output and SVG rasterization.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;If your project doesn't need the full power of Spatie Media Library — associations, conversions, responsive images — and you just want to serve the right image size without pre-generating everything, &lt;code&gt;laravel-imagepresets&lt;/code&gt; is worth a look.&lt;/p&gt;

&lt;p&gt;Install it, define a few presets, drop &lt;code&gt;@imagepreset()&lt;/code&gt; into your Blade templates, and you're done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require fomvasss/laravel-imagepresets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ &lt;a href="https://github.com/fomvasss/laravel-imagepresets" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://packagist.org/packages/fomvasss/laravel-imagepresets" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions or found a bug? Open an issue on GitHub.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>laravel</category>
      <category>performance</category>
      <category>php</category>
    </item>
  </channel>
</rss>
