Laravel Response Cache v8 is here: now offers flexible caching original
Our laravel-responsecache package speeds up your app by caching entire responses on the server. When the same page is requested again, the cached response is served without hitting your controller at all.
We just released v8, a new major version with a powerful new feature: flexible caching. It uses a stale-while-revalidate strategy, so that every visitor gets a fast response, even when the cache is being refreshed.
Let me walk you through it.
Caching responses
The basic usage hasn't changed. Add the CacheResponse middleware to a route, and the full response gets cached:
use Spatie\ResponseCache\Middlewares\CacheResponse; Route::get('/posts', function () { return view('posts'); })->middleware( CacheResponse::for(minutes(30)) );
Every request within those 10 seconds gets the stored response instantly. Your controller doesn't run at all. Depending on the complexity of your page, this can greatly increase the performance of your app.
Flexible caching
Regular caching works great, but it has one downside. When the cache expires, the next visitor has to wait while the server generates a fresh response.
That visitor is the unlucky one. On a page that takes a while to render, say a dashboard with complex queries, their experience is noticeably slower.
Flexible caching solves this with the FlexibleCacheResponse middleware:
use Spatie\ResponseCache\Middlewares\FlexibleCacheResponse; Route::get('/dashboard', function () { return view('dashboard'); })->middleware( FlexibleCacheResponse::for( lifetime: minutes(10), grace: minutes(5), ) );
There are two parameters: a lifetime and a grace period.
During the lifetime, responses are served from the server cache, just like regular caching. When the lifetime expires, instead of leaving visitors hanging, the grace period kicks in.
The grace period
This is the key part. When a request arrives during the grace period, two things happen simultaneously:
The stale response is sent to the browser immediately. The visitor doesn't wait at all. At the same time, using Laravel's defer, the server runs your controller after the response is already sent. The fresh result gets stored in the cache.
The next visitor gets the fresh response, also instantly.
Every visitor gets a fast page. The regeneration happens in the background, invisible to your users.
Regular vs flexible
With regular caching, the first request after expiry is slow: the browser has to wait for the server. With flexible caching, the grace period acts as a safety net. Stale content is served instantly while the server regenerates a fresh response in the background via defer.
Only when the cache is completely gone, past both lifetime and grace, does a visitor have to wait. On a page with regular traffic, this rarely happens.
Replacers
When you cache an entire response, some parts of the HTML might need to stay dynamic. Think of CSRF tokens: every user session needs a fresh one, but the rest of the page can stay cached.
Replacers solve this. Before storing a response, a replacer swaps dynamic content with a placeholder. When serving the cached response, it replaces the placeholder with a fresh value. The package ships with a CsrfTokenReplacer out of the box, so forms just work.
You can create your own replacer by implementing the Replacer interface:
use Spatie\ResponseCache\Replacers\Replacer; use Symfony\Component\HttpFoundation\Response; class UserNameReplacer implements Replacer { protected string $placeholder = '<username-placeholder>'; public function prepareResponseToCache(Response $response): void { $content = $response->getContent(); $userName = auth()->user()?->name ?? 'Guest'; $response->setContent(str_replace( $userName, $this->placeholder, $content, )); } public function replaceInCachedResponse(Response $response): void { $content = $response->getContent(); $userName = auth()->user()?->name ?? 'Guest'; $response->setContent(str_replace( $this->placeholder, $userName, $content, )); } }
Then register it in the config:
// config/responsecache.php 'replacers' => [ \Spatie\ResponseCache\Replacers\CsrfTokenReplacer::class, \App\Replacers\UserNameReplacer::class, ],
This way you can cache the full page while keeping small parts of it personalized per user or per session.
Using attributes
Instead of applying middleware in your routes file, you can use PHP attributes directly on your controllers. Put #[Cache] on a class to cache all its methods, or on a specific method to cache just that one.
use Spatie\ResponseCache\Attributes\Cache; use Spatie\ResponseCache\Attributes\NoCache; #[Cache(lifetime: 5 * 60)] class PostController { public function index() { // cached for 5 minutes } #[NoCache] public function store() { // not cached } }
The #[NoCache] attribute lets you opt out specific methods when the rest of the controller is cached. This is useful for write operations like store or update.
Flexible caching has its own attribute too:
use Spatie\ResponseCache\Attributes\FlexibleCache; class DashboardController { #[FlexibleCache(lifetime: 3 * 60, grace: 12 * 60)] public function index() { return view('dashboard'); } }
I like this approach because caching behavior lives right next to the code it applies to. No need to check the routes file to figure out what's cached and what isn't.
Server-side caching vs Cloudflare
You might be wondering: why not just use Cloudflare?
Cloudflare is great. It serves cached pages from edge nodes close to the visitor. Your server doesn't even see the request. For public pages like a marketing site or docs, that's perfect.
But Cloudflare doesn't know about your app. It caches at the URL level. It has no idea who's logged in, what role they have, or what session data exists. If you need different responses per user, you're stuck wrestling with Vary headers and cache keys.
Our package runs inside your Laravel app, so it has access to everything. The authenticated user, request parameters, custom cache profiles. You can cache a page differently for admins and guests. You can invalidate the cache when a model changes. You can use replacers to keep parts of a cached response dynamic.
You can also use both. Cloudflare for your fully public pages, laravel-responsecache for anything that needs application-aware caching. They work well together.
In closing
Flexible caching is the headline feature of v8. For most apps, adding a grace period is a no-brainer: you get the same caching benefits with zero slow requests during regeneration.
You can find the code of the package on GitHub. We also have extensive docs on our website.
This is one of the many packages we've created at Spatie. If you want to support our open source work, consider picking up one of our paid products.