Create beautiful Open Graph images with Browsershot and Tailwind CSS
For Artisan School, I wanted to create custom Open Graph images for each video page. For example, when somebody shares a link on social media, platforms like Facebook and Twitter use the OG image to represent the shared link. There are many services to create and serve OG images, but I didn't want to depend on an external service. For the website, I already had a Laravel application up and running with Tailwind CSS.
The routes file for this app is quite simple. Based on the slug URI segment of the route, I fetch the video from the database and return a Blade view:
use App\Models\Video; Route::get('/{slug}', function ($slug) { $video = Video::whereSlug($slug)->firstOrFail(); return view('video', ['video' => $video]);});
I added a second route, which also returns a Blade view, just to render the OG image.
Route::get('/{slug}/openGraphImage', function ($slug) { $video = Video::whereSlug($slug)->firstOrFail(); return view('videoOpenGraphImage', ['video' => $video]);})->name('video.openGraphImage');
These images are typically 1200x630 pixels, so I created a container with those dimensions and centered it on the page. Be sure to enable Tailwind's JIT mode to get the square-bracket notation to work. In this container, I added the Artisan School logo and the title of the video.
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="{{ mix('css/app.css') }}"> </head> <body class="flex items-center justify-center min-h-screen"> <div class="bg-blue-500 w-[1200px] h-[630px]"> <!-- your content --> </div> </body></html>
Now how do we store an image out of this Blade view? There's a gorgeous package called Browsershot by Spatie. It converts a webpage to an image or PDF using a headless version of Google Chrome. It needs some setup, but it's straightforward to use. I added a method within my Video model that handles the conversion and saves the image to the public folder of my app.
use Illuminate\Database\Eloquent\Model;use Illuminate\Support\Facades\Storage;use Spatie\Browsershot\Browsershot; class Video extends Model{ public function saveOpenGraphImage() { $path = Storage::disk('public')->path("{$this->slug}.jpg"); Browsershot::url(route('video.openGraphImage', ['slug' => $this->slug])) ->waitUntilNetworkIdle() ->showBackground() ->windowSize(1200, 630) ->setScreenshotType('jpeg', 100) ->save($path); } public function getOpenGraphImageUrl(): string { return Storage::disk('public')->url("{$this->slug}.jpg"); }}
To make my life easy, I also added a method to return the full URL of the OG image. You can use this in the header section of the HTML page or with an SEO package like artesaos/seotools.
<meta property="og:image" content="{{ $video->getOpenGraphImageUrl() }}">