Protone Media logo

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() }}">

Related posts

Want to stay up-to-date on Laravel news and packages?

Pascal Baljet on Twitter

Follow me on Twitter: @pascalbaljet

Pascal Baljet on Twitter

Subscribe to my YouTube channel

© 2013-2024 Protone Media B.V. Hosted with Eddy Server Management.