spatie/laravel-og-image
Generate Open Graph images in Laravel from Blade-defined HTML. Automatically renders screenshots, serves them from a route, and caches files. Templates reuse your app’s CSS, fonts, and Vite assets—no external API required.
Image URLs are based on an md5 hash of the HTML content. When you change the template content, the hash changes and a new URL is produced. Crawlers pick up the new image automatically. Old images remain on disk until you clear them.
By default, images are stored on the public disk at og-images/. You can change both via the OgImage facade:
use Spatie\OgImage\Facades\OgImage;
OgImage::disk('s3', 'og-images');
If your site is behind Cloudflare, no extra configuration is needed. When the first crawler requests an OG image:
Cache-Control: public, max-age=86400All subsequent requests for that image are served directly from Cloudflare's edge. PHP is never hit again (until the cache expires). Since image URLs are content-hashed, different content always produces a different URL, so stale cache is not a concern.
This duration is controlled by the redirect_cache_max_age config value, which sets the Cache-Control: max-age header on the image response. The default is 1 day. Since image URLs are content-hashed, you can safely increase this:
// config/og-image.php
'redirect_cache_max_age' => 60 * 60 * 24 * 7, // 7 days
Without a CDN like Cloudflare, every request to /og-image/{hash}.jpeg hits PHP. This works, but each request occupies a PHP-FPM worker to serve a static file.
You can add an nginx rule that serves already-generated images directly from disk, bypassing PHP entirely:
location ~ ^/og-image/([a-f0-9]+\.(jpeg|jpg|png|webp))$ {
try_files /storage/og-images/$1 /index.php?$query_string;
}
Place this before the location / block in your nginx site config.
This tells nginx to first check if the image exists at /storage/og-images/{hash}.{format}. If it does, nginx serves it directly. If not, the request falls through to PHP, which generates the image and saves it to disk. The next request for that image is served by nginx without PHP.
If you're using Laravel Forge, go to your site's settings and click the Nginx tab. Add the location block above to the "before" section so it is placed before the location / block.
When using S3 as your storage disk, the package detects that it is a remote disk and automatically issues a 301 redirect to the S3 URL instead of streaming the image through PHP. This means:
https://your-bucket.s3.amazonaws.com/og-images/{hash}.jpegThis keeps PHP out of the image serving path entirely. If your S3 bucket is behind CloudFront, crawlers end up fetching the image from CloudFront's edge.
The nginx try_files optimization does not apply to S3, since the files are not on the local filesystem. The redirect approach is used automatically instead.
How can I help you explore Laravel packages today?