Laravel Response Caching, Cache Tags &amp; SWR | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel Performance: HTTP Response Caching, Cache Tags, and Stale-While-Revalidate        On this page       1. [  Beyond cache()-&gt;remember(): Response-Level Caching in Laravel ](#beyond-codecache-gtremembercode-response-level-caching-in-laravel)
2. [  HTTP Response Caching with Middleware ](#http-response-caching-with-middleware)
3. [  Tagged Cache Invalidation ](#tagged-cache-invalidation)
4. [  Stale-While-Revalidate Semantics ](#stale-while-revalidate-semantics)
5. [  Sending Correct HTTP Cache Headers ](#sending-correct-http-cache-headers)
6. [  Key Takeaways ](#key-takeaways)

  ![Laravel Performance: HTTP Response Caching, Cache Tags, and Stale-While-Revalidate](https://cdn.msaied.com/477/5bc44fc7911037277f84b4f08a208ce1.png)

  #laravel   #caching   #performance   #redis  

 Laravel Performance: HTTP Response Caching, Cache Tags, and Stale-While-Revalidate 
====================================================================================

     27 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said  

       Table of contents

1. [  01   Beyond cache()-&gt;remember(): Response-Level Caching in Laravel  ](#beyond-codecache-gtremembercode-response-level-caching-in-laravel)
2. [  02   HTTP Response Caching with Middleware  ](#http-response-caching-with-middleware)
3. [  03   Tagged Cache Invalidation  ](#tagged-cache-invalidation)
4. [  04   Stale-While-Revalidate Semantics  ](#stale-while-revalidate-semantics)
5. [  05   Sending Correct HTTP Cache Headers  ](#sending-correct-http-cache-headers)
6. [  06   Key Takeaways  ](#key-takeaways)

 Beyond `cache()->remember()`: Response-Level Caching in Laravel
---------------------------------------------------------------

Most Laravel apps cache individual values. Fewer cache entire HTTP responses. Fewer still wire up tagged invalidation and stale-while-revalidate (SWR) semantics. Each layer compounds the benefit — here is how to stack them correctly.

---

HTTP Response Caching with Middleware
-------------------------------------

The fastest database query is the one you never make. A dedicated response-cache middleware stores the full serialised response and replays it on subsequent requests.

```php
// app/Http/Middleware/CacheResponse.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class CacheResponse
{
    public function handle(Request $request, Closure $next, int $ttl = 60): mixed
    {
        if ($request->method() !== 'GET') {
            return $next($request);
        }

        $key = 'response:' . sha1($request->fullUrl());

        if ($cached = Cache::get($key)) {
            return response($cached['body'], 200, $cached['headers'])
                ->header('X-Cache', 'HIT');
        }

        $response = $next($request);

        if ($response->isSuccessful()) {
            Cache::put($key, [
                'body'    => $response->getContent(),
                'headers' => $response->headers->all(),
            ], $ttl);
        }

        return $response->header('X-Cache', 'MISS');
    }
}

```

Register it per-route or per-group:

```php
Route::get('/products', ProductController::class)
    ->middleware('cache.response:300');

```

---

Tagged Cache Invalidation
-------------------------

A flat key like `response:sha1(url)` is hard to invalidate when a product changes. Cache tags solve this — but only with Redis or Memcached drivers.

```php
// Storing with tags
Cache::tags(['products', 'product:42'])->put($key, $payload, $ttl);

// Invalidating on model update
class Product extends Model
{
    protected static function booted(): void
    {
        static::saved(function (Product $product) {
            Cache::tags([
                'products',
                "product:{$product->id}",
            ])->flush();
        });
    }
}

```

Update the middleware to accept a variadic tag list:

```php
public function handle(Request $request, Closure $next, string ...$tags): mixed
{
    $key  = 'response:' . sha1($request->fullUrl());
    $store = empty($tags) ? Cache::store() : Cache::tags($tags);

    if ($cached = $store->get($key)) {
        return response($cached['body'], 200, $cached['headers'])
            ->header('X-Cache', 'HIT');
    }

    $response = $next($request);

    if ($response->isSuccessful()) {
        $store->put($key, [
            'body'    => $response->getContent(),
            'headers' => $response->headers->all(),
        ], 300);
    }

    return $response->header('X-Cache', 'MISS');
}

```

Route registration:

```php
Route::get('/products/{product}', ShowProduct::class)
    ->middleware('cache.response:products,product:42');

```

---

Stale-While-Revalidate Semantics
--------------------------------

SWR serves a stale cached response immediately while refreshing the cache asynchronously. This eliminates the "thundering herd" on expiry.

```php
use Illuminate\Support\Facades\Bus;

public function handle(Request $request, Closure $next, int $ttl = 60, int $grace = 30): mixed
{
    if ($request->method() !== 'GET') {
        return $next($request);
    }

    $key      = 'response:' . sha1($request->fullUrl());
    $graceKey = $key . ':grace';
    $cached   = Cache::get($key);

    if ($cached) {
        // If within grace period, serve stale and revalidate in background
        if (!Cache::has($graceKey)) {
            Cache::put($graceKey, true, $grace);
            dispatch(new RevalidateCachedResponse($request->fullUrl(), $key, $ttl))
                ->afterResponse();
        }

        return response($cached['body'], 200, $cached['headers'])
            ->header('X-Cache', 'STALE');
    }

    $response = $next($request);

    if ($response->isSuccessful()) {
        Cache::put($key, [
            'body'    => $response->getContent(),
            'headers' => $response->headers->all(),
        ], $ttl + $grace); // store for full window
    }

    return $response->header('X-Cache', 'MISS');
}

```

`RevalidateCachedResponse` is a queued job that makes an internal HTTP request (or re-runs the controller action) and writes a fresh entry.

### Sending Correct HTTP Cache Headers

Don't forget to emit `Cache-Control` so CDNs and browsers participate:

```php
return $response
    ->header('Cache-Control', "public, max-age={$ttl}, stale-while-revalidate={$grace}")
    ->header('Vary', 'Accept, Accept-Encoding');

```

---

Key Takeaways
-------------

- **Response-level caching** eliminates DB and render overhead entirely for cacheable GET routes.
- **Cache tags** (Redis/Memcached only) let you invalidate by entity rather than guessing keys.
- **Stale-while-revalidate** prevents thundering-herd expiry spikes without sacrificing freshness.
- Always emit `Cache-Control` headers so CDN layers (Cloudflare, Fastly) can participate.
- Keep the grace-period key TTL shorter than the revalidation job's expected runtime to avoid double-dispatch.
- Vary on `Accept` and `Authorization` when caching API responses to prevent cross-user leakage.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-performance-http-response-caching-cache-tags-and-stale-while-revalidate&text=Laravel+Performance%3A+HTTP+Response+Caching%2C+Cache+Tags%2C+and+Stale-While-Revalidate) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-performance-http-response-caching-cache-tags-and-stale-while-revalidate) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Which Laravel cache drivers support cache tags?        Only the Redis and Memcached drivers support cache tags. The file and database drivers do not. If you call Cache::tags() on an unsupported driver, Laravel throws a BadMethodCallException at runtime. 

      Q02  How do I prevent authenticated users from receiving each other's cached responses?        Include a user-specific component in the cache key (e.g. the authenticated user's ID or role) and add 'Vary: Authorization' to the response headers. Never cache responses that contain user-specific data under a shared key. 

      Q03  Is stale-while-revalidate safe for pages with CSRF tokens or session data?        No. Response caching is only appropriate for stateless, public GET endpoints. Pages that embed CSRF tokens, session flash data, or personalised content must bypass the cache entirely. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Laravel Macro-Free Extensibility: Custom Query Builder Classes and Fluent Scopes](https://cdn.msaied.com/658/b45c3cc06b92a332e526bed9bb1f826d.png) laravel eloquent architecture 

### Laravel Macro-Free Extensibility: Custom Query Builder Classes and Fluent Scopes

Skip global macros and reach for typed, testable custom query builder classes in Laravel. Learn how to bind a...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 11 Sep 2026     1 min read  

  Read    

 ](https://msaied.com/articles/laravel-macro-free-extensibility-custom-query-builder-classes-and-fluent-scopes) [ ![Testing Filament Resources, Actions, and Form Assertions with Pest](https://cdn.msaied.com/655/efd33245cffa553c1dffba29721e0139.png) filament pest testing 

### Testing Filament Resources, Actions, and Form Assertions with Pest

A practical guide to writing reliable Pest tests for Filament v3 resources — covering table actions, form subm...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 11 Sep 2026     4 min read  

  Read    

 ](https://msaied.com/articles/testing-filament-resources-actions-and-form-assertions-with-pest-4) [ ![PayZephyr: One Payment API for Stripe, Paystack, and PayPal in Laravel](https://cdn.msaied.com/657/2cc520b20de249b38bc52120605ff447.png) Laravel Payments Stripe 

### PayZephyr: One Payment API for Stripe, Paystack, and PayPal in Laravel

PayZephyr is a Laravel package that wraps eight payment providers—Stripe, Paystack, PayPal, Flutterwave, and m...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 11 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/payzephyr-one-payment-api-for-stripe-paystack-and-paypal-in-laravel) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
