Zum Inhalt springen
Abstrakte Formen aus Glas und dunklem Papier: eine dünne schwebende Platte über einem schweren Block, die dessen Muster kopiert

Redis als Cache-Layer in Statamic: Patterns, die wirklich greifen

Vom Cache-Driver bis zur Tag-basierten Invalidation: wie Sie Redis in Statamic 6 so einsetzen, dass Static Caching schnell bleibt und gleichzeitig sauber invalidiert, sobald Redakteure speichern.

6 min Lesezeit Statamic

Driver-Wahl

Erst den Cache-Driver, dann die Patterns

Statamic erbt sein Cache-Setup direkt aus Laravel. Das heißt: Alles, was Sie aus Laravel über Cache-Driver kennen, gilt eins zu eins. Bevor wir an Static Caching und Invalidation gehen, stellen wir sicher, dass der Default-Cache-Driver Redis ist - file-Cache skaliert auf mehreren App-Nodes nicht, und database-Cache wirft Ihnen in derselben Tabelle Locks, die Sie gerade vermeiden wollten.

Ein gemeinsamer Bürokühlschrank, jede Anwendung hat ihre eigene beschriftete Dose, die Dose dieser Anwendung ist orange.

config/cache.php verstehen

In config/cache.php legen Sie fest, welcher Store für Cache::-Aufrufe verwendet wird. Wichtig sind drei Parameter: default (welcher Store), prefix (damit mehrere Apps sich denselben Redis teilen können) und die Connection-Definition in config/database.php unter redis.cache. Den prefix immer explizit setzen - sonst überschreiben Staging und Production gegenseitig denselben Key.

Ein oranger Akkuschrauber neben einem einfachen Schraubendreher: dieselbe Arbeit, nur um ein Vielfaches schneller.

phpredis statt predis

Laravel unterstützt zwei Redis-Clients: phpredis (C-Extension) und predis (Pure-PHP). Phpredis ist drei- bis fünfmal schneller, weil die Serialisierung in C läuft. Setzen Sie REDIS_CLIENT=phpredis und installieren Sie pecl install redis. Predis brauchen Sie nur, wenn die Extension auf Ihrer Plattform nicht verfügbar ist - auf Laravel Cloud und Hetzner-Ubuntu ist sie es immer.

config/cache.php php
return [
    'default' => env('CACHE_STORE', 'redis'),

    'stores' => [
        'redis' => [
            'driver' => 'redis',
            'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
            'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
        ],

        // Eigener Store nur für Statamic Static Caching - auf einer
        // separaten Redis-DB, damit Cache::flush() nicht das ganze
        // Static Cache wegräumt.
        'statamic_static' => [
            'driver' => 'redis',
            'connection' => 'statamic_static',
            'lock_connection' => 'default',
        ],
    ],

    // Prefix pro Environment, damit Staging und Production sich nicht
    // gegenseitig die Keys überschreiben, falls sie denselben Redis nutzen.
    'prefix' => env('CACHE_PREFIX', env('APP_NAME', 'app') . '_cache_' . env('APP_ENV', 'production')),
];

// .env (Auszug)
// REDIS_CLIENT=phpredis
// REDIS_HOST=127.0.0.1
// REDIS_PORT=6379
// CACHE_STORE=redis
// REDIS_CACHE_CONNECTION=cache

Static Caching

Statamic Static Caching auf Redis

Statamic kennt drei Caching-Strategien: keine, Half-Measure (Antlers wird gerendert, aber Datenbank-Hits gespart) und Full Measure (kompletter HTML-Response wird gecacht). Half Measure ist der Sweet Spot, wenn Sie personalisierte Bereiche haben - Full Measure für reine Marketing-Seiten. Beide funktionieren mit Redis als Backing-Store.

Ein Bündel fertig gedruckter Zeitungen mit oranger Banderole: einmal gedruckt, danach nur noch ausgeliefert.

Half-Measure vs. Full-Measure

Half-Measure speichert das Ergebnis der Statamic-Tags, nicht das fertige HTML - Antlers wird trotzdem gerendert. Full-Measure schreibt den kompletten Response in den Cache und liefert ihn beim zweiten Request, ohne überhaupt PHP zu starten. Auf Marketing-Sites mit zehntausenden Hits pro Tag haben wir mit Full-Measure auf Redis Time-to-First-Byte von 600 ms auf 12 ms gedrückt.

Eine Schablone für den immer gleichen Teil: ein Sprühstoß aus der Dose, und Navigation und Fußzeile stehen orange auf der Seite.

Stash-Tag mit Persistent Cache

Der {{ cache }}-Tag (in Statamic 6 als Stash-Variante verfügbar) cacht Template-Fragmente granular - zum Beispiel eine Navigation oder einen Footer, die seitenweit identisch sind. In Kombination mit einem dedizierten Cache-Store landen die Fragmente in Redis und überleben Deployments. Wichtig: scope=true setzen, wenn der Inhalt vom aktuellen Entry abhängt.

config/statamic/static_caching.php php
return [
    // 'half' lässt Antlers rendern, cached aber Tag-Resultate.
    // 'full' cached den kompletten HTML-Response. 'null' = aus.
    'strategy' => env('STATAMIC_STATIC_CACHING_STRATEGY', 'half'),

    'strategies' => [
        'half' => [
            'driver' => 'cache',
            'expiry' => null, // null = unendlich, wir invalidieren via Hook
        ],

        'full' => [
            'driver' => 'application',
            'expiry' => null,
            'lock_hold_length' => 0,
        ],
    ],

    // Eigener Cache-Store, damit Cache::flush() das Static Cache
    // nicht versehentlich wegräumt.
    'cache_store' => 'statamic_static',

    // Regeln, wann beim Speichern eines Entries invalidiert wird.
    'invalidation' => [
        'class' => null, // Default. Erweitern wir gleich per Hook.
        'rules' => [
            'collections' => [
                'pages'     => ['urls' => ['/', '/{slug}']],
                'tutorials' => ['urls' => ['/de/tutorials', '/de/tutorials/{slug}']],
                'glossary'  => ['urls' => ['/de/glossar', '/de/glossar/{slug}']],
            ],
        ],
    ],

    // Pfade, die niemals gecacht werden (CP, Login, dynamische APIs).
    'exclude' => [
        '/cp', '/cp/*',
        '/!/*',
        '/api/*',
    ],
];

Cache-Setup, das hält?

Wir richten Redis-Cluster, Statamic Static Caching und Invalidation-Hooks für Sie ein - inklusive Last-Test und Runbook.

Performance-Audit anfragen

Invalidation

Caching ohne Invalidation ist nur eine schöne Lüge

Der schwierige Teil am Caching ist nie das Schreiben, sondern das gezielte Wegwerfen. Mit Cache-Tags, Statamic-Events und einer TTL als Sicherheitsnetz bauen Sie ein Setup, das auch nach 18 Monaten Produktion noch frische Inhalte liefert.

Ein Bündel Stifte, von einem orangen Gummiband gehalten: die ganze Gruppe wird auf einmal weggeräumt, nicht Stift für Stift.

Cache-Tags für gruppierte Invalidation

Cache::tags(['glossary', 'nav'])->remember(...) speichert Einträge gruppiert. Beim Speichern eines Glossar-Eintrags reicht Cache::tags('glossary')->flush(), statt alle 4.000 Einzel-Keys einzeln zu löschen. Tags funktionieren in Laravel ausschließlich mit dem Redis- und Memcached-Driver - einer der Hauptgründe, warum wir file-Cache in Production grundsätzlich vermeiden.

Eine Reihe Dominosteine: der orange erste Stein stößt genau die Steine an, die zur Änderung gehören.

Hooks bei EntrySaved und EntryDeleted

Statamic feuert Events bei jeder relevanten Content-Änderung. EntrySaved, EntryDeleted, GlobalSetSaved und TermSaved sind die wichtigsten. Wir hängen Listener daran, die genau die betroffenen Cache-Tags räumen - nicht den ganzen Cache. So bleiben unbeteiligte Seiten warm, und der Cold-Start-Schauer nach jedem Speichern fällt weg.

Ein Milchkarton mit orangem Haltbarkeitsstempel: spätestens dann kommt ein frischer in den Kühlschrank.

TTL als Sicherheitsnetz

Selbst mit perfekten Hooks vergessen Sie irgendwann eine Invalidation. Eine moderate TTL (z. B. 24 Stunden auf Tag-Gruppen, 7 Tage auf Static Caching) sorgt dafür, dass spätestens dann Frische zurückkehrt. TTL ist Bequemlichkeit, nicht Strategie - die eigentliche Arbeit machen die Hooks.

app/Providers/CacheInvalidationServiceProvider.php php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
use Statamic\Events\EntrySaved;
use Statamic\Events\EntryDeleted;
use Statamic\Events\GlobalSetSaved;
use Statamic\Events\TermSaved;
use Statamic\StaticCaching\Cacher;

class CacheInvalidationServiceProvider extends ServiceProvider
{
    public function boot(Cacher $staticCacher): void
    {
        // Entry geändert → nur die betroffenen Tag-Gruppen räumen.
        EntrySaved::listen(function (EntrySaved $event) use ($staticCacher) {
            $entry = $event->entry;
            $handle = $entry->collection()->handle();

            // App-Cache: Tags pro Collection.
            Cache::tags([$handle, 'nav'])->flush();

            // Statamic Static Cache: nur diese URL invalidieren.
            $staticCacher->invalidateUrl($entry->url());
        });

        // Gelöschter Entry → URL aus Static Cache räumen.
        EntryDeleted::listen(function (EntryDeleted $event) use ($staticCacher) {
            $url = $event->entry->url();
            if ($url) {
                $staticCacher->invalidateUrl($url);
            }
            Cache::tags([$event->entry->collection()->handle()])->flush();
        });

        // Global-Set (z. B. Navigation, Footer) → Site-weit räumen.
        GlobalSetSaved::listen(function () use ($staticCacher) {
            Cache::tags(['nav', 'footer', 'globals'])->flush();
            $staticCacher->flush();
        });

        // Taxonomie (z. B. Tags, Categories) → betroffenen Handle räumen.
        TermSaved::listen(function (TermSaved $event) {
            Cache::tags([$event->term->taxonomy()->handle()])->flush();
        });
    }
}

Hat das geholfen? Wir machen Audit, Workshop oder Coaching auch live

Pakete direkt im Shop. Unser Standard-Tagessatz liegt bei 1.500 € netto.

Performance-Audit anfragen