--- title: "Content caching with Statamic" description: "Cachin' checks, breakin' (bottle)necks." pubDate: 2025-05-11 heroImage: "/images/blog/img_9206.jpeg" tags: [laravel] --- So this past week, I rewrote my website again (pause for audible gasp). I've had many different versions of my website over the years, each a reflection of my current technology obsession at the time. From Hugo, to Next.js, to Astro, back to Next.js, then Nuxt, then Svelte, then back to Nuxt... you get the picture. I think I've spent more time rebuilding my own personalized blogging engine than actually writing meaningful content. That's boring though, and neither here nor there. I've landed on the final iteration of it (for real this time, I swear) with Laravel and Statamic. I've been working a lot with Statamic lately building websites for some clients and have quickly placed it as my go to CMS. It's approachable, extremely flexible, and offers a good balance of customization and ease of use with the built in utilities it provides. I took the past week to migrate my hacked up blogging engine that was essentially markdown files manually/programmatically parsed with [CommonMark for PHP](https://commonmark.thephpleague.com/) that heavily relied on [shiki](https://github.com/shikijs/shiki) for code highlighting (by way of Spatie's [shiki-php](https://github.com/spatie/shiki-php)) and shoved in a SQLite database. It was nice being in full control of the pipeline (I even [wrote](/blog/content-driven-websites-with-php-and-laravel) about it!), but these days, I just want to focus on writing and content rather than building yet another blog engine (Y.A.B.E.) to scratch my own itch. That's where [Statamic](https://statamic.com/) comes in. With it, I centralize all of my content in flat files and focus on writing. I use a lot of code examples in all my writing, so naturally I wanted to bring over Shiki for highlighting. Luckily, Statamic makes it easy to hook into their markdown parsing process, providing a [sensible](https://github.com/statamic/cms/blob/5.x/src/Markdown/Parser.php#L79) set of defaults for parsing said markdown with the PHP League's CommonMark package. All that's needed is one additional bit of configuration in my `AppServiceProvider.php` to highlight code with Shiki, again with the help of Statie's shiki-php: ```php final class AppServiceProvider extends ServiceProvider { public function boot(): void { self::configureMarkdown(); // Other stuff... } private function configureMarkdown(): void { Markdown::addExtensions(function () { return [ new HighlightCodeExtension(theme: 'one-dark-pro'), ]; }); } // Other stuff... } ``` This works™️, but unfortunately Shiki is quite resource intensive on a browser page due to all the custom styling that goes into each section of highlighted code. Throw in fancy things like blurred or focused lines, and you can easily weigh down a page's load time pretty significantly. An easy way to get around this is using Statamic's [cache tag](https://statamic.dev/tags/cache#tag-parameters-and-contents). For a blog page, my [Antler's](https://statamic.dev/antlers) file (Statamic's custom DSL for markup) looks like this: ```html
final
class
AppServiceProvider
extends
ServiceProvider
{
public
function
boot
():
void
{
// Other stuff...
self
::
configureEvents
();
}
private
function
configureEvents
():
void
{
Event
::
listen
(
function
(
EntrySaved
$event
) {
/**
@var
array{slug:
string} $entry */
$entry
=
$event
->
entry
;
if
(
Cache
::
has
(
$entry
[
'slug'
])) {
Cache
::
forget
(
$entry
[
'slug'
]);
}
});
}
// Other stuff...
}
```
Not to mention the fact I'm not tree shaking themes or languages that I'm not using either. I probably could... but I'll save that for a rainy day.
So that's great that we cache the content, but we need _someone_ to trigger the caching in the first place. And that sucks. I don't know about you, but I'm not waiting 20 seconds for a blog post to load so I can read it. So how do we get around that?
[Commands](https://laravel.com/docs/12.x/artisan) to rescue!
With Statamic, while templating with Antlers offers a seamless experience for pulling content out of storage onto the page, it also gives us the ability to [query content](https://statamic.dev/content-queries) in an Eloquent-like fashion. Using this approach, I'm able to spin up a simple command to run on deployment whenever I push new content:
```php
final class WarmBlogCacheCommand extends Command
{
/**
* @var string
*/
protected $signature = 'app:warm-blog-cache';
/**
* @var string|null
*/
protected $description = 'Warms the Statamic cache with shiki content for the blog.';
public function handle(): void
{
/** @var EntryCollection $entries */
$entries = Entry::query() // @phpstan-ignore-line
->where('collection', 'blogs')
->where('published', true)
->get(['content']);
$this->info("Warming cache for {$entries->count()} blog entries...");
/** @var \Statamic\Entries\Entry $entry */
foreach ($entries as $entry) {
/** @var string $key */
$key = $entry->slug();
$this->info("Caching: $key");
/** @var string $content */
$content = $entry->get('content');
Cache::rememberForever($key, fn () => Markdown::parser('default')->parse($content));
$this->info("Cached: $key");
}
/** @var \Statamic\Entries\Entry $entry */
foreach ($entries as $entry) {
/** @var string $key */
$key = $entry->slug();
if (! Cache::has($key)) {
$this->error("Cache missing for: $key");
}
}
$this->info('Blog cache warming complete!');
}
}
```
Nothing fancy in the above. I pull all of the entries in my blog collection, run them through the markdown parser that's using Spatie's Shiki extension for code highlighting with the use of `Markdown::parser('default')`, and voilà - no one has to pay the price for waiting for content to be parsed!
I've gotta fire this command _somewhere_ though. My site lives on a Hetzner VPS that's provisioned by Forge (as the good Lord intended). To re-cache all the entries, I'm able to plop this command right into my deploy script:
```bash
cd /home/forge/joeymckenzie.tech
git pull origin $FORGE_SITE_BRANCH
$FORGE_COMPOSER install --no-dev --no-interaction --prefer-dist --optimize-autoloader
# Prevent concurrent php-fpm reloads
touch /tmp/fpmlock 2>/dev/null || true
( flock -w 10 9 || exit 1
echo 'Reloading PHP FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9 Deployment Complete!
```
Unfortunately, _someone_ still has to pay the price when I bust the cached entries when I'm editing blog content in production, though I try to make sure that's always myself. The beauty of Statamic storing content in flat files is that my locally developed changes are always in sync with what's deployed. So a quick fix here or there, deploy, re-cache the content, and boom. All in a day's work.
Well, that's it for now. And if you haven't tried Statamic yet, this is your sign.
Until next time, friends!