Dynamic blurred image placeholders for Laravel + Vue 3

Maintainers

Package info

github.com/alik-laravel/image

pkg:composer/alik-laravel/image

Fund package maintenance!

alik-laravel

Statistics

Installs: 6

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-04-26 13:01 UTC

This package is not auto-updated.

Last update: 2026-04-26 13:04:15 UTC


README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

Dynamic blurred image placeholders for Laravel + Vue 3.

Drop a <Image src="/storage/images/foo.jpg" /> Vue 3 component into your page and ship the smooth fade-in UX you're used to from Medium / Vercel without manually generating low-quality preview images. Behind the scenes a Laravel route at /storage/images/{image}-placeholder.{ext} generates a 40px blurred version on first request, persists it next to the original on the configured disk, and serves subsequent requests directly through the web server (no PHP boot).

Contents

  1. Installation
  2. Quick start
  3. Configuration
  4. Drivers
  5. Operations
  6. Production checklist
  7. Documentation

Installation

composer require alik-laravel/image

Publish the Vue component and config:

php artisan vendor:publish --tag=alik-image-components
php artisan vendor:publish --tag=alik-image-config

Run the storage symlink (required for the second-request fast path):

php artisan storage:link

Wire Vite + TypeScript + Tailwind v4 in the consumer app:

// vite.config.ts
import path from 'node:path'

export default defineConfig({
    resolve: {
        alias: {
            '@alik-image': path.resolve(__dirname, 'vendor/alik-laravel/image/resources/js'),
        },
    },
})
// tsconfig.json
{
    "compilerOptions": {
        "paths": {
            "@alik-image/*": ["./vendor/alik-laravel/image/resources/js/*"]
        }
    }
}
/* resources/css/app.css */
@source '../../vendor/alik-laravel/image/resources/js/**/*.{vue,ts}';

Quick start

<script setup lang="ts">
import { Image } from '@alik-image'
</script>

<template>
    <Image src="/storage/images/avatar.jpg" alt="User avatar" aspect-ratio="1/1" />
</template>

The component renders two <img> layers: a 40px blurred placeholder (auto-derived URL /storage/images/avatar-placeholder.jpg) and the original. Once the original loads it fades into view over 700ms. Respects prefers-reduced-motion.

Configuration

The config file config/image.php is published by vendor:publish --tag=alik-image-config:

'route' => [
    'enabled' => true,
    'prefix' => env('IMAGE_ROUTE_PREFIX', 'storage/images'),
    'middleware' => ['web'],
],
'source' => [
    'disk' => env('IMAGE_SOURCE_DISK', 'public'),
    'path' => env('IMAGE_SOURCE_PATH', 'images'),
],
'placeholder' => [
    'driver' => env('IMAGE_PLACEHOLDER_DRIVER', 'spatie'),
    'width' => 40,
    'blur' => 30,
    'quality' => 100,
    'max_source_bytes' => 20 * 1024 * 1024,
    'visibility' => 'inherit',
    'persist_to_disk' => true,
    'drivers' => [
        'imagemagick' => ['binary' => env('IMAGEMAGICK_PATH', '/usr/bin/convert')],
    ],
],

See docs/installation.md for full wiring details.

Drivers

Two backends ship out of the box:

  • spatie (default) — uses spatie/image v3 with the imagick PHP extension.
  • imagemagick — shells out to the convert binary. Useful when imagick PHP ext isn't available.

Switch via env: IMAGE_PLACEHOLDER_DRIVER=imagemagick.

Custom drivers register through the manager:

app(\Alik\Image\Drivers\PlaceholderManager::class)->extend('mydriver', fn () => new MyDriver());

See docs/drivers.md for the full driver contract and ImageMagick hardening notes.

Operations

After changing width/blur/quality/driver in config, run:

php artisan alik-image:flush

Pre-generate placeholders for an existing image library (deploy hook):

php artisan alik-image:warm --chunk=100 --continue-on-error

Local-dev symlink for hot-reloading the package's Vue files in your host app:

php artisan alik-image:link    # replaces resources/js/vendor/alik-image with symlink
php artisan alik-image:unlink  # removes symlink, republishes the real files

Production checklist

  1. Cache driver: redis/memcached/database/dynamodb (NOT fileCache::lock is unreliable on file driver).
  2. Run php artisan storage:link AND verify your web server falls back to index.php for unknown static paths (try_files $uri $uri/ /index.php?$query_string in nginx, default Laravel .htaccess in Apache). Without this fallback the package can't generate the first request.
  3. PHP imagick extension OR the convert binary on PATH (configure via IMAGEMAGICK_PATH).
  4. PHP exif extension (hard requirement of spatie/image).
  5. Optional: hook alik-image:warm into your deploy pipeline for a known-set image directory.

Documentation

Testing

composer test           # Pest
composer analyse        # PHPStan level 10
composer format         # Pint
npm test                # Vitest (component tests)
npm run type-check      # vue-tsc

Previewing images in the workbench

The package ships a Testbench workbench app with a /preview route so you can eyeball real placeholder generation against real images.

  1. Drop sample images into workbench/storage/app/public/images/ (any .jpg/.png/.webp). The blade view at workbench/resources/views/preview.blade.php looks for sample-1.jpg, sample-2.jpg, sample-3.jpg by default — rename your files or edit the view to match.

  2. Link workbench storage so the web server can serve /storage/images/...:

    composer build      # alias for: testbench workbench:build (runs storage:link inside the workbench)
  3. Boot the workbench server:

    composer serve      # alias for: testbench serve --ansi
  4. Open http://127.0.0.1:8000/preview. The first request to each <img src="/storage/images/foo.jpg"> triggers the package's /storage/images/{image}-placeholder.{ext} route, which generates a 40px blurred copy on disk. Subsequent requests are served by the web server directly (no PHP boot) — confirm by tailing workbench/storage/logs/laravel.log or watching the file appear next to the original.

To exercise the actual <Image> Vue component (fade-in, reduced-motion, error states), wire it into a host app or run the Vitest spec at tests/js/Image.spec.ts. The blade preview intentionally uses plain <img> tags so it has no Vite build dependency.

To reset cached placeholders between driver/config changes:

php vendor/bin/testbench alik-image:flush

Changelog

See CHANGELOG.md.

Credits

License

The MIT License (MIT). See LICENSE.md.