alik-laravel / image
Dynamic blurred image placeholders for Laravel + Vue 3
Fund package maintenance!
Requires
- php: ^8.4
- ext-exif: *
- laravel/framework: ^13.0
- spatie/image: ^3.8
- spatie/laravel-package-tools: ^1.90
Requires (Dev)
- driftingly/rector-laravel: ^2.3
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1
- orchestra/testbench: ^11.0
- orchestra/workbench: ^11.0
- pestphp/pest: ^4.5
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.1
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/extension-installer: ^1.3
This package is not auto-updated.
Last update: 2026-04-26 13:04:15 UTC
README
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
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) — usesspatie/imagev3 with the imagick PHP extension.imagemagick— shells out to theconvertbinary. 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
- Cache driver: redis/memcached/database/dynamodb (NOT
file—Cache::lockis unreliable on file driver). - Run
php artisan storage:linkAND verify your web server falls back toindex.phpfor unknown static paths (try_files $uri $uri/ /index.php?$query_stringin nginx, default Laravel.htaccessin Apache). Without this fallback the package can't generate the first request. - PHP
imagickextension OR theconvertbinary on PATH (configure viaIMAGEMAGICK_PATH). - PHP
exifextension (hard requirement ofspatie/image). - Optional: hook
alik-image:warminto your deploy pipeline for a known-set image directory.
Documentation
docs/installation.md— full vendor:publish, Vite alias, Tailwind@source, middleware, route prefix overridedocs/drivers.md— driver contract, custom drivers, ImageMagick hardeningdocs/component.md— Vue prop reference, accessibility, theme tokensdocs/controllers.md— route shape, response headers, signed-URL recipedocs/testing.md— testing patterns for consuming apps
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.
-
Drop sample images into
workbench/storage/app/public/images/(any.jpg/.png/.webp). The blade view atworkbench/resources/views/preview.blade.phplooks forsample-1.jpg,sample-2.jpg,sample-3.jpgby default — rename your files or edit the view to match. -
Link workbench storage so the web server can serve
/storage/images/...:composer build # alias for: testbench workbench:build (runs storage:link inside the workbench) -
Boot the workbench server:
composer serve # alias for: testbench serve --ansi -
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 tailingworkbench/storage/logs/laravel.logor 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.