# Laravel Response Cache Laravel Response Cache is a performance optimization package that speeds up Laravel applications by caching entire HTTP responses. When a request comes in, the middleware checks if a cached response exists and serves it immediately, bypassing the entire application stack including routing, controllers, and database queries. This can dramatically reduce response times from hundreds of milliseconds to just a few milliseconds for subsequent requests. The package is highly configurable and supports features like per-user caching, selective URL caching, cache tagging, custom cache profiles, CSRF token replacement, and programmatic cache management. It works with any Laravel cache driver and automatically handles serialization, cache key generation, and response restoration. By default, it caches all successful GET requests returning text-based content (HTML, JSON) for one week, but every aspect can be customized through configuration or custom implementations. ## Installation and Configuration ```bash # Install via Composer composer require spatie/laravel-responsecache # Publish configuration file php artisan vendor:publish --tag="responsecache-config" ``` ```php // config/responsecache.php return [ 'enabled' => env('RESPONSE_CACHE_ENABLED', true), 'cache_profile' => Spatie\ResponseCache\CacheProfiles\CacheAllSuccessfulGetRequests::class, 'cache_lifetime_in_seconds' => env('RESPONSE_CACHE_LIFETIME', 60 * 60 * 24 * 7), 'cache_store' => env('RESPONSE_CACHE_DRIVER', 'file'), 'add_cache_time_header' => env('APP_DEBUG', true), 'cache_time_header_name' => 'laravel-responsecache', 'replacers' => [ \Spatie\ResponseCache\Replacers\CsrfTokenReplacer::class, ], 'hasher' => \Spatie\ResponseCache\Hasher\DefaultHasher::class, 'serializer' => \Spatie\ResponseCache\Serializers\DefaultSerializer::class, 'cache_tag' => '', ]; ``` ```php // bootstrap/app.php (Laravel 11+) ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \Spatie\ResponseCache\Middlewares\CacheResponse::class, ]); $middleware->alias([ 'doNotCacheResponse' => \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class, ]); }) ``` ```php // app/Http/Kernel.php (Laravel 10 and earlier) protected $middlewareGroups = [ 'web' => [ \Spatie\ResponseCache\Middlewares\CacheResponse::class, ], ]; protected $middlewareAliases = [ 'doNotCacheResponse' => \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class, ]; ``` ## Clear Entire Cache ```php use Spatie\ResponseCache\Facades\ResponseCache; // Clear all cached responses ResponseCache::clear(); // Clear responses with specific tags (requires cache driver that supports tags) ResponseCache::clear(['products', 'api']); ``` ```bash # Clear cache via Artisan command php artisan responsecache:clear # Clear cache for specific URL php artisan responsecache:clear --url=/products ``` ```php // Clear cache automatically on model events namespace App\Traits; use Spatie\ResponseCache\Facades\ResponseCache; trait ClearsResponseCache { public static function bootClearsResponseCache() { self::created(function () { ResponseCache::clear(); }); self::updated(function () { ResponseCache::clear(); }); self::deleted(function () { ResponseCache::clear(); }); } } // Apply to model class Product extends Model { use ClearsResponseCache; } ``` ## Forget Specific URLs ```php use Spatie\ResponseCache\Facades\ResponseCache; // Forget single URL ResponseCache::forget('/products/123'); // Forget multiple URLs (array syntax) ResponseCache::forget(['/products/123', '/products/456']); // Forget multiple URLs (variadic syntax) ResponseCache::forget('/products/123', '/products/456'); // Note: forget() only works without cacheNameSuffix // For suffix-based caching, use selectCachedItems() ``` ## Select and Forget Cache Items with Advanced Criteria ```php use Spatie\ResponseCache\Facades\ResponseCache; // Forget all PUT responses for specific URL ResponseCache::selectCachedItems() ->withPutMethod() ->forUrls('/products/update') ->forget(); // Forget multiple URLs with specific method ResponseCache::selectCachedItems() ->withPutMethod() ->forUrls(['/products/123', '/products/456']) ->forget(); // Forget with user-specific suffix (default: user ID or empty string) ResponseCache::selectCachedItems() ->usingSuffix('100') ->forUrls('/dashboard') ->forget(); // Complex selection with all criteria ResponseCache::selectCachedItems() ->withPutMethod() ->withHeaders(['Accept' => 'application/json']) ->withCookies(['session_token' => 'abc123']) ->withParameters(['page' => '1', 'limit' => '20']) ->withRemoteAddress('192.168.1.100') ->usingSuffix('100') ->usingTags('products', 'api') ->forUrls('/api/products', '/api/categories') ->forget(); ``` ## Prevent Specific Routes from Caching ```php // Apply middleware to single route Route::get('/auth/logout', function() { return redirect('/login'); })->middleware('doNotCacheResponse'); Route::get('/checkout', [CheckoutController::class, 'index']) ->middleware('doNotCacheResponse'); // Apply middleware to route group Route::middleware('doNotCacheResponse')->group(function () { Route::get('/admin/dashboard', [AdminController::class, 'dashboard']); Route::get('/admin/users', [AdminController::class, 'users']); }); ``` ```php // Apply in controller constructor class UserController extends Controller { public function __construct() { $this->middleware('doNotCacheResponse', [ 'only' => ['edit', 'update', 'destroy'] ]); $this->middleware('doNotCacheResponse', [ 'except' => ['index', 'show'] ]); } public function index() { // This WILL be cached return view('users.index'); } public function edit($id) { // This will NOT be cached return view('users.edit'); } } ``` ## Cache Specific Routes with Custom Lifetime ```php // Register as route middleware (if not globally applied) protected $middlewareAliases = [ 'cacheResponse' => \Spatie\ResponseCache\Middlewares\CacheResponse::class, ]; // Cache route for 5 minutes (300 seconds) Route::get('/trending', [ProductController::class, 'trending']) ->middleware('cacheResponse:300'); // Cache route group for 10 minutes (600 seconds) Route::middleware('cacheResponse:600')->group(function() { Route::get('/featured', [ProductController::class, 'featured']); Route::get('/bestsellers', [ProductController::class, 'bestsellers']); }); // Cache indefinitely until manually cleared Route::get('/static-content', [PageController::class, 'about']) ->middleware('cacheResponse:604800'); // 1 week ``` ## Use Cache Tags for Selective Clearing ```php use Spatie\ResponseCache\Middlewares\CacheResponse; // Add single tag with lifetime Route::get('/products', [ProductController::class, 'index']) ->middleware('cacheResponse:300,products'); // Add single tag without custom lifetime (uses default) Route::get('/categories', [CategoryController::class, 'index']) ->middleware('cacheResponse:categories'); // Add multiple tags with lifetime Route::middleware('cacheResponse:300,products,api')->group(function() { Route::get('/api/products', [ApiProductController::class, 'index']); Route::get('/api/products/{id}', [ApiProductController::class, 'show']); }); // Use static helper method for clarity Route::get('/featured', [ProductController::class, 'featured']) ->middleware(CacheResponse::using(300, 'products', 'featured')); ``` ```php // Clear only tagged cache entries ResponseCache::clear(['products']); // Clears /products and /api/products ResponseCache::clear(['products', 'api']); // Clears only items with BOTH tags // Alternative: Use Laravel's native cache tagging Cache::tags('products')->flush(); // Same effect ``` ## Bypass Cache Temporarily with Headers ```bash # Configure environment variables CACHE_BYPASS_HEADER_NAME=X-Skip-Cache CACHE_BYPASS_HEADER_VALUE=secret-key-12345 ``` ```bash # Request with bypass header gets fresh response curl -H "X-Skip-Cache: secret-key-12345" https://example.com/products # Without header, gets cached response curl https://example.com/products ``` ```php // Use in application code for debugging use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'X-Skip-Cache' => config('responsecache.cache_bypass_header.value'), ])->get('https://example.com/api/products'); // Useful for profiling, debugging, or monitoring ``` ## Create Custom Cache Profile ```php namespace App\CacheProfiles; use DateTime; use Illuminate\Http\Request; use Spatie\ResponseCache\CacheProfiles\CacheProfile; use Symfony\Component\HttpFoundation\Response; use Carbon\Carbon; class CacheOnlyPublicPages implements CacheProfile { public function enabled(Request $request): bool { return config('responsecache.enabled'); } public function shouldCacheRequest(Request $request): bool { // Don't cache authenticated users if (auth()->check()) { return false; } // Only cache GET requests if (! $request->isMethod('get')) { return false; } // Don't cache AJAX requests if ($request->ajax()) { return false; } // Don't cache admin routes if ($request->is('admin/*')) { return false; } return true; } public function shouldCacheResponse(Response $response): bool { // Only cache successful responses if (! $response->isSuccessful()) { return false; } // Only cache HTML content $contentType = $response->headers->get('Content-Type', ''); if (! str_starts_with($contentType, 'text/html')) { return false; } return true; } public function cacheRequestUntil(Request $request): DateTime { // Cache homepage longer than other pages if ($request->path() === '/') { return Carbon::now()->addHours(24); } // Cache product pages for 1 hour if ($request->is('products/*')) { return Carbon::now()->addHours(1); } // Default: 30 minutes return Carbon::now()->addMinutes(30); } public function useCacheNameSuffix(Request $request): string { // Different cache per locale return app()->getLocale(); // Or per user role (for authenticated users) // return auth()->check() ? auth()->user()->role : 'guest'; // Or per device type // return $request->mobile() ? 'mobile' : 'desktop'; } } ``` ```php // config/responsecache.php 'cache_profile' => App\CacheProfiles\CacheOnlyPublicPages::class, ``` ## Create Custom Replacer for Dynamic Content ```php namespace App\Replacers; use Spatie\ResponseCache\Replacers\Replacer; use Symfony\Component\HttpFoundation\Response; class UserNameReplacer implements Replacer { protected string $placeholder = ''; public function prepareResponseToCache(Response $response): void { $content = $response->getContent(); if (! $content || ! auth()->check()) { return; } // Replace actual username with placeholder before caching $username = auth()->user()->name; $response->setContent( str_replace($username, $this->placeholder, $content) ); } public function replaceInCachedResponse(Response $response): void { $content = $response->getContent(); if (! $content || ! auth()->check()) { return; } // Replace placeholder with current user's name when serving cache $username = auth()->user()->name; $response->setContent( str_replace($this->placeholder, $username, $content) ); } } ``` ```php // config/responsecache.php 'replacers' => [ \Spatie\ResponseCache\Replacers\CsrfTokenReplacer::class, \App\Replacers\UserNameReplacer::class, ], ``` ```php // Another example: Current time replacer class CurrentTimeReplacer implements Replacer { protected string $placeholder = ''; public function prepareResponseToCache(Response $response): void { $content = $response->getContent(); if (! $content) return; // Replace time elements with placeholder $response->setContent(preg_replace( '/