Workaround an image issue with Statamic's SSG

Published June 9th, 2023

I’ll be upfront – I don’t use Statamic’s Static Site Generator (SSG), however one client who wanted to handle their own deployment requested we ensure their Statamic site runs with the SSG so they can deploy through Netlify. When the site launched in 2021, everything was working fine, but an update to either Statamic or the SSG started to create issues.

There’s been a long-running issue discussing the problem on Github, but basically the SSG is unable to copy the glide-rendered images in to the deployed directory.

Workaround 1: asset caching

  • Urgh, local editing is a nuissance.

A workaround mentioned in the thread is to turn on image caching (so the cache directory, i.e. public/img, can be copied during build), however that makes editing locally challenging as images are aggressively cached. This is great for production, but when you’re trying to tinker with cropping or focal points, having to manually clear the cache each time is a nuisance.

Workaround 2: updating code within the SSG package

  • Crazy talk. Update vendor files? Nuts. No.

Another workaround is to add a couple of lines of code to the bindGlide function within SSG’s src/Generator.php – more details are on the Github comment. Basically this goes after the $directory variable is set:

1config([
2    'statamic.assets.image_manipulation.cache' => true,
3    'statamic.assets.image_manipulation.cache_path' => $this->config['destination'].'/'.$directory,
4]);
Copied!

With this in place, building the site works as expected. Nearly.

A by-product of this step is that the public/img directory starts to get populated – which is good for build but bad for local use – so the editability was a bit of an issue still.

And, as we all know, changing a file in your vendor folder is just a total no-no, so there’s that too. Just to re-iterate, don’t change files in any package in your vendor folder – you’re asking for a world of pain for long-term maintenance when your changes get lost by another update.

The question then is – how can this be included in my client’s site?

As it turns out, really easily, and ticks off not touching vendor files, and also working flawlessly with local editability too.

Workaround 3: code within the actual site

  • Use the AppServiceProvider to have this as part of the site's codebase. Yes, please!

The basic gist of this solution is:

  1. When running the ssg:generate command, configure Statamic to cache image manipulations (like workaround 1)

  2. After the site is generated, clear the image cache and reset configuration

Statamic’s SSG makes the second part possible with its super handy after callback.

OK so let’s get things running first – how do we know we’re running the ssg:generate command? We can look at the $_SERVER['argv'] property to see what arguments have been passed – which is when please (or artisan) is running via the command line only.

We can add this to our AppServiceProvider boot method.

1if (isset($_SERVER['argv']) && count($_SERVER['argv']) >= 2 && (
2 ($_SERVER['argv'][0] === 'please' && $_SERVER['argv'][1] === 'ssg:generate')
3 ||
4 ($_SERVER['argv'][0] === 'artisan' && $_SERVER['argv'][1] === 'statamic:ssg:generate')
5    )) {
6 // ...
7}
Copied!

Essentially what we’re doing here is:

  1. Checking the argv property exists

  2. Has at least 2 arguments

  3. Checking that these arguments are what we expect for an ssg:generate command, with support for either a please or artisan call 

While SSG’s docs say to call php please ssg:generate, the please command is essentially shortcutting to the Statamic namespace:

1php artisan statamic:ssg:generate
Copied!

And hey, someone may use it. So just in case, my logic covers it.

So within this if statement, there are those two steps to do.

First of all, let’s get the original configuration, and update to enable image caching:

1if (isset($_SERVER['argv']) && count($_SERVER['argv']) >= 2 && (
2 ($_SERVER['argv'][0] === 'please' && $_SERVER['argv'][1] === 'ssg:generate')
3 ||
4 ($_SERVER['argv'][0] === 'artisan' && $_SERVER['argv'][1] === 'statamic:ssg:generate')
5 )) {
6 // get the directory
7 $directory = Arr::get(config('statamic.ssg'), 'glide.directory');
8 
9 // get the original config
10 $originalCacheConfig = config('statamic.assets.image_manipulation');
11 
12 // update the config to enable image manipulation caching
13 config([
14 'statamic.assets.image_manipulation.cache' => true,
15 'statamic.assets.image_manipulation.cache_path' => config('statamic.ssg.destination').DIRECTORY_SEPARATOR.$directory,
16 ]);
17}
Copied!

Pretty straight forward, and essentially exactly what the Github comment suggests.

SSG comes with an after callback too, which is run (funnily enough) after the build process is completed.

1SSG::after(function () use ($originalCacheConfig) {
2 // ...
3});
Copied!

Notice here we’re also passing the $originalCacheConfig in, as we will use that.

In this callback, we’re re-setting the config to what it was, and clearing the image cache by calling the please (aka artisan statamic) command:

1SSG::after(function () use ($originalCacheConfig) {
2 // reset the config to what it was before the script ran
3 config([
4 'statamic.assets.image_manipulation.cache' => $originalCacheConfig['cache'],
5 'statamic.assets.image_manipulation.cache_path' => $originalCacheConfig['cache_path'],
6 ]);
7 
8 // clear the image cache
9 Artisan::call('statamic:glide:clear');
10});
Copied!

And that’s it – when the ssg:generate command is run, we’re updating the config on the fly, and then allowing the command to run – and when it’s done, clearing the image cache to maintain local editability.

And what do we get? A site that builds as expected, with local image editability, and a happy client. All in all, not a bad effort.

Here's the entire AppServiceProvider just for a complete view:

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Support\Facades\Artisan;
6use Illuminate\Support\ServiceProvider;
7use Statamic\StaticSite\SSG;
8use Statamic\Support\Arr;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 /**
13 * Register any application services.
14 */
15 public function register(): void
16 {
17 //
18 }
19 
20 /**
21 * Bootstrap any application services.
22 */
23 public function boot(): void
24 {
25 if (isset($_SERVER['argv']) && count($_SERVER['argv']) >= 2 && (
26 ($_SERVER['argv'][0] === 'please' && $_SERVER['argv'][1] === 'ssg:generate')
27 ||
28 ($_SERVER['argv'][0] === 'artisan' && $_SERVER['argv'][1] === 'statamic:ssg:generate')
29 )) {
30 // get the directory
31 $directory = Arr::get(config('statamic.ssg'), 'glide.directory');
32 
33 // get the original config
34 $originalCacheConfig = config('statamic.assets.image_manipulation');
35 
36 // update the config to enable image manipulation caching
37 config([
38 'statamic.assets.image_manipulation.cache' => true,
39 'statamic.assets.image_manipulation.cache_path' => config('statamic.ssg.destination').DIRECTORY_SEPARATOR.$directory,
40 ]);
41 
42 SSG::after(function () use ($originalCacheConfig) {
43 // reset the config to what it was before the script ran
44 config([
45 'statamic.assets.image_manipulation.cache' => $originalCacheConfig['cache'],
46 'statamic.assets.image_manipulation.cache_path' => $originalCacheConfig['cache_path'],
47 ]);
48 
49 // clear the image cache
50 Artisan::call('statamic:glide:clear');
51 });
52 }
53 }
54}
Copied!

Why not submit a PR?

To be totally honest, I’ve considered it – but not using SSG myself day-to-day, I’m not 100% sure this is the best fix for every use case or if this adds any other repercussions that may be specific to actual users, whether that be a Netlify user or some other deployment target – so hey, if you’ve stumbled across this post and want to connect, reach out to me via the contact page here and let’s have a chat.

Happy to work through this in more detail with daily SSG users.

You may be interested in...