Stop Pre-Generating Image Thumbnails in Laravel — Do It…
    Neura MarketNeura Market/Perplexity
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityTrending
    PerplexityBlogStop Pre-Generating Image Thumbnails in Laravel — Do It On-The-Fly Instead
    Back to Blog
    Stop Pre-Generating Image Thumbnails in Laravel — Do It On-The-Fly Instead
    backend

    Stop Pre-Generating Image Thumbnails in Laravel — Do It On-The-Fly Instead

    Vas May 7, 2026
    0 views

    Every Laravel project eventually hits the same wall: a designer asks for a new image size, and...

    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.

    There's a simpler way. Generate image variants on demand, cache the result permanently, and move on.

    That's exactly what laravel-imagepresets does.


    The Problem With Pre-Generated Thumbnails

    When you pre-generate image variants (the approach Spatie Media Library encourages by default), you pay upfront:

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

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


    What Is laravel-imagepresets?

    It's a Laravel package built on League/Glide that gives you:

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

    Installation

    composer require fomvasss/laravel-imagepresets
    php artisan vendor:publish --tag=imagepresets-config
    

    The service provider is auto-discovered. No manual registration needed.


    Your First Image URL

    // Resize to 800px wide, convert to WebP
    $url = imagepreset_url('storage/images/photo.jpg', ['w' => 800, 'fm' => 'webp']);
    // → https://example.com/imagepreset?fm=webp&src=storage%2Fimages%2Fphoto.jpg&w=800
    

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


    Named Presets: Define Once, Use Everywhere

    Hardcoding ['w' => 300, 'h' => 200, 'fm' => 'webp', 'fit' => 'crop'] everywhere is a maintenance headache. Named presets solve this.

    In config/imagepresets.php:

    'presets' => [
        'thumb'     => ['w' => 300, 'h' => 200, 'fm' => 'webp', 'fit' => 'crop'],
        'hero'      => ['w' => 1200, 'fm' => 'webp', 'q' => 85],
        'avatar'    => ['w' => 96,  'h' => 96,  'fm' => 'webp', 'fit' => 'crop'],
        'og_banner' => ['w' => 1300, 'h' => 650, 'fit' => 'fill-max', 'fm' => 'jpg', 'bg' => 'ffffff'],
    ],
    

    Then in your code:

    // String shorthand
    $url = imagepreset_url('photo.jpg', 'thumb');
    
    // Facade
    Imagepreset::url('photo.jpg', 'hero');
    
    {{-- Blade directive --}}
    <img src="@imagepreset('photo.jpg', 'thumb')" alt="Thumbnail">
    

    Need to override one param? Pass it alongside the preset name:

    // Use thumb preset but output JPG instead of WebP
    $url = imagepreset_url('photo.jpg', ['preset' => 'thumb', 'fm' => 'jpg']);
    

    Fit Methods Explained

    The fit parameter controls how the image fills the target dimensions. Choosing the wrong one is a common source of stretched or oddly-cropped images.

    FitUse when...
    cropYou need exact pixel dimensions (cards, avatars). Edges may be trimmed.
    containThe full image must be visible. No fill — transparent space is left.
    fillFull image visible, remaining canvas filled with bg color. May upscale.
    fill-maxLike fill but never upscales. Great for OG images and social banners.
    maxLike contain but never upscales beyond original size.
    stretchForces exact dimensions ignoring aspect ratio. Rarely a good idea.

    For OG images, fill-max is your friend:

    $url = imagepreset_url('post-image.jpg', [
        'w' => 1300, 'h' => 650,
        'fit' => 'fill-max',
        'fm' => 'jpg',
        'bg' => 'ffffff',
    ]);
    

    S3 / Remote Disk Support

    Just set the disk in your .env:

    IMAGEPRESET_DISK=s3
    IMAGEPRESET_PATH=imagepresets
    

    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.


    Security Out of the Box

    Open image-resizing endpoints are a common attack surface. The package handles the main threats:

    Allowlists prevent arbitrary dimensions from being requested:

    'allowed_widths'  => [100, 200, 400, 800, 1200],
    'allowed_heights' => [100, 200, 400, 600],
    'allowed_formats' => ['webp', 'jpg', 'png'],
    

    SSRF protection blocks remote image sources that point to private IPs or localhost.

    Image bomb protection rejects files that exceed max_image_pixels (default: 150 Mpx).

    Signed URLs (optional) make it impossible to tamper with parameters:

    IMAGEPRESET_SIGNED_URL=true
    

    Once enabled, imagepreset_url() automatically generates HMAC-signed URLs. Changing any parameter returns 403.


    Race Condition Protection

    What happens when 50 users simultaneously request the same uncached image? Without protection, you'd process the same image 50 times.

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


    Audit Log: Discover What Your Frontend Actually Needs

    Before locking down allowlists in production, you can enable audit logging in development to see exactly which params your frontend requests:

    IMAGEPRESET_AUDIT_LOG=true
    

    Then extract unique values from your logs:

    grep -oh '"w":[0-9]*' storage/logs/*.log | sort -u
    

    Use the findings to populate your allowlists before deploying.


    CDN and Nginx Caching

    Every response ships with:

    Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable
    ETag: "<hash>"
    

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

    To verify your cache is working:

    # Run twice — first should be MISS, second HIT
    curl -s -o /dev/null -D - "https://example.com/imagepreset?src=photo.jpg&w=800&fm=webp"
    

    Clearing the Cache

    # Clear all cached presets
    php artisan imagepresets:clear
    
    # Clear a specific remote disk
    php artisan imagepresets:clear --disk=s3 --path=imagepresets
    

    Requirements

    DependencyVersion
    PHP^8.1
    Laravel10 / 11 / 12 / 13
    league/glide^2.0 | ^3.0

    Optional: imagick extension for AVIF output and SVG rasterization.


    Wrapping Up

    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, laravel-imagepresets is worth a look.

    Install it, define a few presets, drop @imagepreset() into your Blade templates, and you're done.

    composer require fomvasss/laravel-imagepresets
    

    → GitHub · Packagist


    Have questions or found a bug? Open an issue on GitHub.

    Tags

    backendlaravelperformancephp

    Comments

    More Blog

    View all
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    Stay up to date

    Get the latest Perplexity prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Perplexity and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this Perplexity resource

    • Automate SEO-Optimized Blog Creation with GPT-4, Perplexity AI & Multi-Language Supportn8n · $24.99 · Related topic
    • Automate SEO Blog Content Creation with GPT-4, Perplexity AI, and WordPressn8n · $24.99 · Related topic
    • Automate SEO Blog Creation + Social Media with GPT-4, Perplexity, and WordPressn8n · $24.99 · Related topic
    • Auto-Generate SEO Blog Posts with Perplexity, GPT, Leonardo & WordPressn8n · $14.99 · Related topic
    Browse all workflows