Auto Generate Thumbnails for Your Blog Posts with Laravel

By Priyash Patil | Updated: Oct 17, 2023 9PM IST

Manually creating and uploading thumbnails for each blog post can be a time-consuming task, but fear not! With Laravel, a powerful PHP framework, you can automate the process of generating thumbnails for your blog posts. In this blog post, we’ll explore how to leverage Laravel to generate thumbnails effortlessly using wkhtmltoimage and laravel-snappy.

How it will work?

The flow of the image generation process would be:

  1. Using Laravel’s view methods to build the HTML output.
  2. Then we use that HTML and pass it to wkhtmltoimage library through laravel-snappy package. Which can return the binary data of a rendered image.
  3. We then use that data to store it as an image on desired file storage.

Installing Laravel Snappy

Laravel Snappy is an open-source package developed by Barry vd. Heuvel. This package is built as a wrapper for Laravel on package snappy. Snappy use wkhtmltopdf and wkhtmltoimage tool for the generation of PDFs and Image.

Install the Laravel Snappy package by running the following command.

composer require barryvdh/laravel-snappy

Then publish the snappy config file using the following command:

php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"

This will publish the snappy config file at config/snappy.php. Update the file as per below:

<?php

return [
    'pdf' => [
        'enabled' => false,
        'binary'  => env('WKHTML_PDF_BINARY', '/usr/bin/wkhtmltopdf'),
        'timeout' => false,
        'options' => [],
        'env'     => [],
    ],

    'image' => [
        'enabled' => true,
        'binary'  => env('WKHTML_IMG_BINARY', '/usr/bin/wkhtmltoimage'),
        'timeout' => false,
        'options' => [],
        'env'     => [],
    ],

];

Notice we are updating the binary paths to be configurable by .env file.

Configuring snappy for local and production use

For this post, we are interested in the wkhtmltoimage library. This library comes bundled with wkhtmltopdf. Whether you are running the application locally or in production. You’ll need to install the wkhtmltopdf library. You can download the wkhtmltopdf library from wkhtmltopdf.org.

During the installation keep a note of the library’s installation location.

Windows

Download the suitable installer for your system and follow the installer steps to install the library. You can download the Windows install from here: Download Windows Install

Mac

You can install the library using HomeBrew. Run the following command:

brew install wkhtmltopdf

Linux

On Ubuntu systems, you can install the library by running:

sudo apt-get install -y wkhtmltopdf

Docker and Laravel Sail

If you are running the application in docker make sure to add the install statement in the docker build file. Also, if you are using laravel sail then you will need to customize the docker files. To do so run the following commands.

sail artisan sail:publish

This will publish the docker build file in your project root. Then you can update the specific Dockerfile and add the following line under:

RUN apt-get update && apt-get install -y wkhtmltopdf

After the install the usual library locations are:

After the installation update the .env with binary path variables:

WKHTML_PDF_BINARY=/usr/local/bin/wkhtmltopdf 
WKHTML_IMG_BINARY=/usr/local/bin/wkhtmltoimage

The var WKHTML_PDF_BINARY is not required for the scope of this post but can add it for later use.

Prepare Image/HTML template

Now the libraries are configured. We can work on the template from which will HTML be rendered as an Image.

Create a view file at resources/views/templates/simple.blade.php. Then add the following code.

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Featured Image</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link
        href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
        rel="stylesheet">
    <style>
        html {
            box-sizing: border-box;
            font-size: 16px;
        }

        html body {
            font-family: 'Roboto', sans-serif;
            margin: 0;
            padding: 0;
        }

        *,
        *:before,
        *:after {
            box-sizing: inherit;
        }

        body,
        h1,
        h2,
        h3,
        h4,
        h5,
        h6,
        p,
        ol,
        ul {
            margin: 0;
            padding: 0;
            font-weight: normal;
            line-height: normal;
        }

        ol,
        ul {
            list-style: none;
        }

        img {
            max-width: 100%;
            height: auto;
        }

        .featured-container {
            width: 1200px;
            height: 628px;
            display: flex;
            align-items: center;
            position: relative;
        }

        .background {
            background-image: url('https://cdn.priyashpatil.com/polka-pattern.jpg');
            background-repeat: repeat-x;
            background-size: auto 100%;
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            z-index: -1;
            fill-opacity: 0.8;
        }

        .content-padding {
            padding: 80px;
        }

        .head-1 {
            font-size: 60px;
            font-weight: bold;
        }

        .text-center {
            text-align: center;
        }

        .brand-logo {
            position: absolute;
            top: 80px;
            left: 80px;
            width: 200px;
            height: auto;
        }
    </style>
</head>

<body>
    <div class="featured-container content-padding">
        <img src="https://cdn.priyashpatil.com/pps-logo.png" class="brand-logo">
        <h1 class="head-1 text-center">{{ $title }}</h1>
        <div class="background"></div>
    </div>
</body>

</html>

You have to keep the templates simple and less dependent on external resources to avoid any rendering issues. Also do note that some CSS properties do not work well with wkhtmltopdf so, you have to try and test the template to suit your needs.

Generating and Storing the image

For this post let’s assume you have a blog post Model with a title as property and you want to generate and update the post instance. Let’s say we have and post endpoint /admin/blog/{blogPost}/generate-thumbnail which you can trigger using a simple form in frontend. For example:

routes/web.php

<?php

use App\Http\Controllers\PostFeaturedImageGenerateController; 

Route::post('/admin/blog/{blogPost}/generate-thumbnail', PostFeaturedImageGenerateController::class)->name('admin.posts.generate-thumbnail');

Then you can create an invokable controller as app\Http\Controllers\PostFeaturedImageGenerateController.php:

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Barryvdh\Snappy\Facades\SnappyImage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class PostFeaturedImageGenerateController extends Controller
{
    /**
     * Handle incoming request.
     */
    public function __invoke(Request $request,Post $blogPost)
    {
        // Prep new image
        $fileName = strtolower((string) Str::ulid());
        $snappyImage = SnappyImage::loadView('templates/simple', ['title' => $blogPost->title]);

        // Save generated image
        $filePath = "thumbnails/{$fileName}.jpeg";
        Storage::put($filePath, $snappyImage->output());

        // Update blog post
        $blogPost->thumbnail = $filePath;
        $blogPost->save();

        return redirect()->back();
    }
}

Then in your admin panel, you can add the form like so:

<form action="{{ route('admin.posts.generate-thumbnail', $blogPost->id) }}" method="POST">
    @csrf
    <button type="submit" class="btn btn-sm btn-secondary">Generate Featured Image</button>
</form>

Rendering images directly during template development

During the development of the template, you may want to directly render the image into the browser instead of storing it in the filesystem. Snappy offers a couple of methods for it.

You can return images in browsers using the following methods:

$snappyImage = SnappyImage::loadView('templates/simple', ['title' => $blogPost->title]);
return $snappyImage->inline();

or

$snappyImage = SnappyImage::loadView('templates/simple', ['title' => $blogPost->title]);
return $snappyImage->stream();

Conclusion

Congratulations on successfully generating thumbnails based on the blog title! This post demonstrates an efficient method using the snappy package to generate captivating thumbnail images. By incorporating the Lottery Helper class, you can expand your options by including additional templates and adding a touch of randomness to the selection process. Furthermore, you have the flexibility to automate the thumbnail generation process upon saving or updating your blog. Feel free to unleash your creativity and explore endless possibilities for creating visually appealing thumbnails that perfectly complement your content. Let your imagination soar and continue to innovate!