Laravel Response Caching, Cache Tags &amp; SWR | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel Performance: HTTP Response Caching, Cache Tags, and Stale-While-Revalidate

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

 Go beyond simple cache()-&gt;remember() calls. Learn how to combine HTTP response caching, tagged cache invalidation, and stale-while-revalidate semantics to serve fast, consistent responses at scale.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 27 Jul 2026 · Updated 27 Jul 2026 · 3 min read

ShareCopy linkCopied

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

  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)

 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.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [caching](https://www.msaied.com/public/articles?search=caching)
- [performance](https://www.msaied.com/public/articles?search=performance)
- [redis](https://www.msaied.com/public/articles?search=redis)

 Frequently asked questions 
---------------------------

  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.

   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.

   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.

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

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

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleLaravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Contracts](https://www.msaied.com/public/articles/laravel-api-resources-sparse-fieldsets-conditional-relationships-and-stable-contracts) [Next articleQueue-SQL: Run Mass Deletes and Updates Across Parallel Laravel Queue Jobs](https://www.msaied.com/public/articles/queue-sql-run-mass-deletes-and-updates-across-parallel-laravel-queue-jobs)  

   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)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
