Zum Inhalt springen
Alle Artikel

CDN-Caching für eine Laravel-Anwendung: was Cloudflare halten darf

16. Sept. 2026 · DevOps · 3 min

·Von Dimitri Pisarev

Hinter Cloudflare zerfällt eine Laravel-Anwendung in drei Cache-Schichten: statische Assets am Edge für ein Jahr, öffentliche Seiten kurz mit stale-while-revalidate, und sitzungsgebundene Antworten nie gecacht. Die gesamte Aufgabe von Laravel ist es, präzise Cache-Control-Header zu schicken. Das Edge folgt ihnen mechanisch. Unpräzise Header kosten die Hit-Rate exakt so, wie sie geschrieben sind.

Welche Antworten darf das Edge cache-t?#

SchichtHältTypische DirektiveGesetzt durch
BrowserGehashte Assets, Fontsmax-age=31536000, immutableDateiname-Hashing beim Build
CDN-EdgeÖffentliche Seiten, cachebare GET-Endpunktes-maxage=120, stale-while-revalidate=600App-Middleware
AnwendungGerenderte Fragmente, Query-ErgebnisseLaravel-Cache oder Redis, Minuten bis StundenCode

Die Edge-Schicht ist die spannende, weil sie geteilt ist: Eine Kopie der Landing-Page bedient alle Besucher. Deshalb schaltet ein einziges Session-Cookie auf dieser Seite das Edge-Caching stillschweigend für alle ab.

Die Cloudflare-Voreinstellungen überraschen einmal#

Standardmäßig cache-t Cloudflare laut eigener Dokumentation nur Antworten auf statische Dateiendungen (css, js, Bilder, Fonts). HTML läuft ungecacht durch, mit einem cf-cache-status: DYNAMIC-Header, außer eine Cache-Regel sagt etwas anderes. Jede Antwort mit Set-Cookie umgeht den Cache. Laravel schickt standardmäßig an jeden neuen Besucher ein Session-Cookie. Das Out-of-the-Box-Verhalten ist also: nirgendwo HTML gecacht, und nichts im Dashboard sagt einem, dass es anders ginge. Die ehrliche Prüfung ist der cf-cache-status-Header pro Antwort.

Die Middleware, die entscheidet#

Zwei Regeln tragen die meiste Last: authentifizierte Anfragen bedeuten private, no-store, und eine kurze Allowlist wirklich öffentlicher Routen bekommt Edge-Direktiven:

class CacheHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);
 
        if ($request->user()) {
            $response->headers->set('Cache-Control', 'private, no-store');
        } elseif (in_array($request->path(), ['/', 'blog', 'impressum'], true)) {
            $response->headers->set(
                'Cache-Control',
                'public, s-maxage=120, stale-while-revalidate=600',
            );
        }
 
        return $response;
    }
}

s-maxage steuert das Edge, max-age den Browser, und stale-while-revalidate erlaubt Cloudflare, bis zu zehn Minuten die letzte Kopie auszuliefern, während im Hintergrund frische geholt wird. Ein Deploy heißt damit höchstens zwei Minuten Staleness. Für die meisten Seiten der richtige Handel.

Um das Session-Cookie verdient ein eigener Satz: StartSession von Routen fernhalten, die am Edge gecacht werden sollen, sonst frisst der Set-Cookie-Bypass die Hit-Rate Seite für Seite. Laravel lässt Session-Middleware pro Route-Gruppe wegfallen, und Marketing-Seiten brauchen selten eine Session.

Gezielt purgen#

Kurze TTLs machen die meisten Purges unnötig. Wenn eine Seite sofort aktuell sein muss, nimmt Cloudflares Purge-API exakte URLs (bis dreißig pro Aufruf) oder die ganze Zone. Invalidation vorab entwerfen: Ein Deploy-Hook, der die fünf URLs purgt, die sich wirklich ändern, schlägt den Notfall „alles purgen“ mit einer Stunde niedriger Hit-Rate.

Gotcha

Query-Strings sind standardmäßig Teil des Cloudflare-Cache-Keys: /blog?page=2 und /blog?utm_source=newsletter sind separate Einträge. Kanonische URLs in der App normalisieren oder Tracking-Parameter in einer Cache-Regel strippen, sonst füllt sich das Edge mit Einträgen, die niemand zweimal ausliefert.

Was in der Anwendungsschicht bleibt#

Personalisierte Fragmente (Warenkörbe, Dashboards, alles pro Nutzer) gehören in Redis mit Tags und TTLs. Dieselbe Dreischichten-Logik. Das Edge behandelt geteilte Seiten, der App-Cache personalisierte, und keine der beiden Schichten sollte die Arbeit der anderen übernehmen: Pro-Nutzer-Inhalte in einem geteilten Edge-Cache sind kein Performance-Feature, sondern ein Datenleck, das auf seine erste authentifizierte Seite wartet.

Verifizieren über Header, nicht allein über das Dashboard: cf-cache-status sollte auf öffentlichen Seiten HIT zeigen, auf authentifizierten DYNAMIC, nie umgekehrt. Ein HIT auf einer eingeloggten Antwort ist kein Performance-Gewinn, sondern ein Vorfall.