An admin dashboard that shows this month's revenue, best-selling products and new customers usually runs the same aggregate queries every time it loads. With ten staff refreshing it all day, the database repeats identical work hundreds of times an hour. The page gets slow even though the numbers barely change.
Caching stores that result once and serves it again without touching the database. This guide covers caching in Laravel 11: choosing a store, the remember() pattern, invalidation that avoids stale data, cache stampedes, and the framework caches you should run on deploy.
1. Measure First
- Install Laravel Debugbar (local) or Telescope to see query counts and timings per page.
- Dozens or hundreds of queries usually means N+1. Fix it with eager loading first.
- Few but slow queries usually means missing indexes on filtered columns.
- Only then cache results that are genuinely expensive and change rarely.
Caching over a bad query only postpones the pain: whoever arrives after expiry still waits.
2. Choosing a Cache Store
Set CACHE_STORE in .env. New Laravel 11 projects default to database.
| Store | Strengths | Weaknesses | Good for |
|---|---|---|---|
| file | No extra service | Slows with many files, not shared across servers | Shared hosting, small apps |
| database | Works out of the box | Adds load to the database you want to relieve | Early stage, low traffic |
| redis | Very fast, supports tags and atomic locks | Needs a Redis server | VPS or cloud, medium to high traffic |
| memcached | Fast and lightweight | Fewer features than Redis | Stacks already running it |
| array | Lives for one request | Not persistent | Tests |
Your code uses the same Cache facade regardless, so moving from file to redis later requires no code changes.
3. The Cache::remember Pattern
use Illuminate\Support\Facades\Cache;
$stats = Cache::remember('dashboard:stats:' . now()->format('Y-m'), now()->addMinutes(10), function () {
return [
'revenue' => Order::where('status', 'paid')
->whereMonth('created_at', now()->month)
->whereYear('created_at', now()->year)
->sum('total'),
'orders' => Order::whereDate('created_at', today())->count(),
'customers' => Customer::whereMonth('created_at', now()->month)->count(),
];
});
- The key includes the period, so a new month starts with a fresh key.
- A short TTL: dashboard numbers may lag a few minutes, not hours.
- Store plain arrays, not large object graphs, to keep entries small.
4. Invalidation
Stale data is worse than a slow page. If a price changes but the catalogue shows the old one for an hour, customers notice. Three strategies:
- TTL only: fine for stats that may lag a little.
- Forget on change: clear keys when a model is saved or deleted.
- Versioned keys: bump a version number so old keys are simply ignored.
// app/Models/Product.php
protected static function booted(): void
{
$flush = function () {
Cache::forget('catalog:featured');
Cache::forget('catalog:categories');
};
static::saved($flush);
static::deleted($flush);
}
With Redis, tags let you clear a group of keys at once:
Cache::tags(['catalog'])->remember('catalog:featured', 3600, fn () => Product::featured()->get());
Cache::tags(['catalog'])->flush();
The file and database stores do not support tags and throw "This cache store does not support tagging".
5. Stampedes and Locks
Suppose a heavy report expires just as 50 users open it. All 50 see an empty cache and run the query at once. Two defences:
// a) Atomic lock: only one process rebuilds
$report = Cache::get('report:monthly');
if ($report === null) {
$report = Cache::lock('report:monthly:lock', 30)->block(10, function () {
return Cache::remember('report:monthly', 3600, fn () => ReportService::build());
});
}
// b) Stale-while-revalidate (Laravel 11.23+)
$report = Cache::flexible('report:monthly', [300, 900], fn () => ReportService::build());
6. Framework Caches on Deploy
php artisan optimize # config, routes, views, events
php artisan optimize:clear # clear them while debugging
After config:cache, any env() call outside the config folder returns null. Read values through config() and keep env() inside config files.
7. What Not to Cache
- Balances, stock at checkout and payment status. Always read them live.
- Per-user data under a shared key. Include the user ID, such as
user:42:cart, or customers may see each other's data. - Full pages containing CSRF tokens or the logged-in user's name.
- Connections or closures that cannot be serialized.
8. Common Errors
- Edits do not show up: the key was never forgotten. Move invalidation into model events.
- "Does not support tagging": tags on file or database stores.
- storage/framework/cache keeps growing: many dynamic keys with long TTLs on the file store.
- .env values missing in production: the
config:cacheeffect.
Caching Checklist
- N+1 problems and indexes fixed before adding cache.
- Keys use clear prefixes and include the user ID for private data.
- Every entry has a TTL or a documented invalidation path.
- Transactional data is never cached.
php artisan optimizeruns in the deploy script.
Caching works best on top of efficient queries, so pair this with optimizing Eloquent query performance, and push heavy recalculation into queues and jobs.