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/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  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) 

 [ ![MySQL Query Profiling in Laravel: EXPLAIN ANALYZE, Slow Query Log, and Index Hints](https://cdn.msaied.com/481/8bfc417cb94b3e2868428e065fe4b166.png) laravel mysql performance 

### MySQL Query Profiling in Laravel: EXPLAIN ANALYZE, Slow Query Log, and Index Hints

Stop guessing why a query is slow. Learn how to use EXPLAIN ANALYZE, Laravel's slow query log listener, and in...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/mysql-query-profiling-in-laravel-explain-analyze-slow-query-log-and-index-hints) [ ![Laravel Custom Eloquent Relations: Building Polymorphic and Composite-Key Relations](https://cdn.msaied.com/480/c7d2c0d0fd1823460fbdb138f77b573f.png) laravel eloquent database 

### Laravel Custom Eloquent Relations: Building Polymorphic and Composite-Key Relations

Eloquent's built-in relations cover 90% of cases, but composite-key joins and non-standard polymorphic pivots...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-custom-eloquent-relations-building-polymorphic-and-composite-key-relations) [ ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Overhead](https://cdn.msaied.com/479/d7c54bd7a42ee57610a29f19a2c5e0fa.png) laravel postgresql jsonb 

### PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Overhead

JSONB columns unlock flexible schemas, but misused they become performance sinkholes. This guide covers GIN in...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-overhead) 

   [  ![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)
