Introduction
Caching is the single most effective technique for improving application performance. By storing frequently accessed data in fast-access storage layers, you can reduce database queries, minimize computation, and deliver responses in milliseconds instead of seconds.
A well-implemented caching strategy can reduce server load by 80%, handle 10x more traffic, and dramatically improve user experience. However, improper caching can lead to stale data, memory issues, and difficult debugging.
In this comprehensive guide, we'll explore advanced caching strategies for Laravel 12, covering cache drivers, multi-layer caching, response optimization, HTTP caching, and cache invalidation patterns. You'll learn how to implement production-ready caching that balances performance with data freshness.
Understanding Laravel Cache Layers
Cache Layer Hierarchy
Laravel supports multiple cache layers:
- OPcache – PHP bytecode caching (configuration level)
- Application Cache – Redis, Memcached, Database (data level)
- HTTP Cache – Response caching, CDN (delivery level)
- Browser Cache – Client-side storage (client level)
Available Cache Drivers
Laravel 12 supports multiple drivers:
- Redis – Recommended for production (fast, feature-rich)
- Memcached – Fast in-memory storage
- Database – Persistent, slower than memory
- File – Simple, suitable for development
- Array – In-memory, request lifecycle only
- DynamoDB – AWS managed cache
Configuring Redis Cache
1. Install and Configure Redis
Install Redis and PHP extension:
# Install Redis
sudo apt-get install redis-server
# Install PHP Redis extension
composer require predis/predis
Configure in .env:
CACHE_DRIVER=redis
REDIS_CLIENT=predis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
2. Configure Multiple Redis Connections
Separate cache and sessions in config/database.php:
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
'session' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_SESSION_DB', '2'),
],
],
Basic Caching Operations
1. Storing Data in Cache
use Illuminate\Support\Facades\Cache;
// Store for specific duration (seconds)
Cache::put('key', 'value', 3600);
// Store for specific duration (Carbon)
Cache::put('key', 'value', now()->addMinutes(60));
// Store permanently
Cache::forever('key', 'value');
// Store if doesn't exist
Cache::add('key', 'value', 3600);
// Store and retrieve
$value = Cache::remember('users', 3600, function () {
return DB::table('users')->get();
});
// Store permanently or retrieve
$value = Cache::rememberForever('settings', function () {
return DB::table('settings')->get();
});
2. Retrieving Cached Data
// Get value
$value = Cache::get('key');
// Get with default
$value = Cache::get('key', 'default');
// Get with closure default
$value = Cache::get('key', function () {
return DB::table('users')->get();
});
// Check if exists
if (Cache::has('key')) {
$value = Cache::get('key');
}
// Get and delete
$value = Cache::pull('key');
3. Removing Cached Data
// Delete specific key
Cache::forget('key');
// Delete multiple keys
Cache::forget(['key1', 'key2', 'key3']);
// Clear entire cache
Cache::flush();
// Using array access
unset(Cache::get('key'));
Advanced Caching Techniques
1. Cache Tags (Redis/Memcached Only)
Group related cache items:
// Store with tags
Cache::tags(['people', 'artists'])->put('John', $john, 3600);
Cache::tags(['people', 'authors'])->put('Anne', $anne, 3600);
// Retrieve tagged items
$john = Cache::tags(['people', 'artists'])->get('John');
// Remember with tags
$posts = Cache::tags(['posts', 'user:' . $userId])
->remember('user_posts:' . $userId, 3600, function () use ($userId) {
return Post::where('user_id', $userId)->get();
});
// Flush all items with a tag
Cache::tags(['people'])->flush(); // Removes both John and Anne
Cache::tags(['artists'])->flush(); // Removes only John
2. Atomic Locks for Cache Stampede Prevention
Prevent multiple processes from regenerating the same cache:
use Illuminate\Support\Facades\Cache;
$value = Cache::lock('cache:posts')->get(function () {
return Cache::remember('posts', 3600, function () {
// This expensive operation runs only once
return Post::with('author', 'tags')->get();
});
});
// With timeout
$lock = Cache::lock('process:import', 120);
if ($lock->get()) {
try {
// Process runs exclusively
$this->importData();
} finally {
$lock->release();
}
}
// Block and wait
$lock = Cache::lock('process:import', 120);
$lock->block(5, function () {
// Waits up to 5 seconds for lock
$this->importData();
});
3. Cache Key Strategies
Design effective cache keys:
// ❌ Poor key structure
Cache::remember('posts', 3600, function () {
return Post::all();
});
// ✅ Descriptive, namespaced keys
Cache::remember('posts:all:published', 3600, function () {
return Post::where('status', 'published')->get();
});
// ✅ Include parameters in key
public function getUserPosts($userId, $status = 'published')
{
$cacheKey = "user:{$userId}:posts:status:{$status}";
return Cache::remember($cacheKey, 3600, function () use ($userId, $status) {
return Post::where('user_id', $userId)
->where('status', $status)
->get();
});
}
// ✅ Use model cache keys
class Post extends Model
{
public function getCacheKey()
{
return sprintf(
'%s/%s-%s',
$this->getTable(),
$this->getKey(),
$this->updated_at->timestamp
);
}
}
Query Result Caching
1. Automatic Query Caching
Cache Eloquent queries efficiently:
// Cache query results
$users = Cache::remember('users:active', 3600, function () {
return User::where('status', 'active')
->with('profile')
->get();
});
// Cache with query builder
$posts = Cache::remember('posts:popular', 3600, function () {
return DB::table('posts')
->where('views', '>', 1000)
->orderBy('views', 'desc')
->limit(10)
->get();
});
// Cache aggregate queries
$totalRevenue = Cache::remember('stats:revenue:today', 600, function () {
return Order::whereDate('created_at', today())
->sum('total_amount');
});
2. Model-Level Caching
Implement caching in models:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
class Post extends Model
{
protected static function booted()
{
// Clear cache on update
static::updated(function ($post) {
Cache::tags(['posts'])->flush();
Cache::forget("post:{$post->id}");
});
// Clear cache on delete
static::deleted(function ($post) {
Cache::tags(['posts'])->flush();
Cache::forget("post:{$post->id}");
});
}
public static function findCached($id)
{
return Cache::remember("post:{$id}", 3600, function () use ($id) {
return static::with(['author', 'tags', 'comments'])->findOrFail($id);
});
}
public static function getAllCached()
{
return Cache::tags(['posts'])->remember('posts:all', 3600, function () {
return static::with('author')->published()->get();
});
}
}
3. Using Remember Method
Simplify caching with the remember method:
// Basic usage
$value = Cache::remember('key', 3600, function () {
return expensiveOperation();
});
// With model relationships
$user = User::with(['posts' => function ($query) {
$query->remember(3600);
}])->find(1);
// Using Rememberable trait (custom implementation)
trait Rememberable
{
public function scopeRemember($query, $seconds = 3600)
{
$key = $this->getCacheKey($query);
return Cache::remember($key, $seconds, function () use ($query) {
return $query->get();
});
}
protected function getCacheKey($query)
{
return md5(
$query->toSql() . serialize($query->getBindings())
);
}
}
Response Caching
1. HTTP Response Caching
Cache entire HTTP responses:
// Install spatie/laravel-responsecache
composer require spatie/laravel-responsecache
// Publish config
php artisan vendor:publish --provider="Spatie\ResponseCache\ResponseCacheServiceProvider"
Configure in config/responsecache.php:
return [
'enabled' => env('RESPONSE_CACHE_ENABLED', true),
'cache_lifetime_in_seconds' => env('RESPONSE_CACHE_LIFETIME', 60 * 60 * 24 * 7),
'cache_store' => env('RESPONSE_CACHE_DRIVER', 'redis'),
];
Use in routes:
use Spatie\ResponseCache\Middlewares\CacheResponse;
Route::get('/posts', [PostController::class, 'index'])
->middleware(CacheResponse::class);
// With custom TTL
Route::get('/posts/{post}', [PostController::class, 'show'])
->middleware('cache.response:3600');
Clear response cache:
use Spatie\ResponseCache\Facades\ResponseCache;
// Clear all
ResponseCache::clear();
// Clear specific URLs
ResponseCache::forget('/posts');
ResponseCache::forget('/posts/1');
2. Cache Control Headers
Set cache headers manually:
public function show(Post $post)
{
return response()
->view('posts.show', compact('post'))
->header('Cache-Control', 'public, max-age=3600')
->header('Expires', now()->addHour()->toRfc7231String());
}
// ETags for conditional requests
public function show(Post $post)
{
$etag = md5($post->updated_at->timestamp);
if (request()->header('If-None-Match') === $etag) {
return response()->noContent(304);
}
return response()
->view('posts.show', compact('post'))
->header('ETag', $etag)
->header('Cache-Control', 'public, max-age=3600');
}
3. Middleware-Based Caching
Create custom cache middleware:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class CacheResponse
{
public function handle(Request $request, Closure $next, $minutes = 60)
{
if ($request->method() !== 'GET') {
return $next($request);
}
$key = 'route:' . $request->fullUrl();
return Cache::remember($key, $minutes * 60, function () use ($next, $request) {
return $next($request);
});
}
}
View and Fragment Caching
1. Blade Fragment Caching
Cache expensive view fragments:
{{-- Cache for 1 hour --}}
@cache('sidebar', 3600)
<div class="sidebar">
<h3>Popular Posts</h3>
<ul>
@foreach(Post::popular()->limit(5)->get() as $post)
<li>{{ $post->title }}</li>
@endforeach
</ul>
</div>
@endcache
{{-- Cache with tags --}}
@cache('user-dashboard', 3600, ['users', 'dashboard'])
<div class="dashboard">
<!-- Expensive dashboard content -->
</div>
@endcache
Create custom Blade directive in AppServiceProvider:
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Cache;
public function boot()
{
Blade::directive('cache', function ($expression) {
return "<?php if(! app('cache.store')->has({$expression})) : ?>";
});
Blade::directive('endcache', function () {
return "<?php endif; ?>";
});
}
2. View Compilation Caching
Cache compiled Blade views:
# Cache views in production
php artisan view:cache
# Clear view cache
php artisan view:clear
Configuration and Route Caching
1. Configuration Caching
Cache all configuration files:
# Cache config
php artisan config:cache
# Clear config cache
php artisan config:clear
Important: Environment variables are not accessible when config is cached. Always use config() helper:
// ❌ Don't use env() directly
$apiKey = env('API_KEY');
// ✅ Use config helper
$apiKey = config('services.api.key');
2. Route Caching
Cache route definitions:
# Cache routes
php artisan route:cache
# Clear route cache
php artisan route:clear
Note: Route caching doesn't work with closure-based routes:
// ❌ Won't work with route cache
Route::get('/dashboard', function () {
return view('dashboard');
});
// ✅ Use controllers
Route::get('/dashboard', [DashboardController::class, 'index']);
Cache Invalidation Strategies
1. Time-Based Invalidation
Simple TTL-based expiration:
// Short TTL for frequently changing data
Cache::put('active_users', $users, 60); // 1 minute
// Medium TTL for moderate changes
Cache::put('popular_posts', $posts, 3600); // 1 hour
// Long TTL for static data
Cache::put('site_settings', $settings, 86400); // 1 day
2. Event-Based Invalidation
Clear cache on model events:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
class Post extends Model
{
protected static function booted()
{
$clearCache = function () {
Cache::tags(['posts'])->flush();
Cache::forget('posts:count');
Cache::forget('posts:popular');
};
static::created($clearCache);
static::updated($clearCache);
static::deleted($clearCache);
}
}
3. Manual Cache Busting
Clear specific caches programmatically:
// In controller after update
public function update(Request $request, Post $post)
{
$post->update($request->validated());
// Clear related caches
Cache::forget("post:{$post->id}");
Cache::tags(['posts', 'user:' . $post->user_id])->flush();
ResponseCache::forget("/posts/{$post->slug}");
return redirect()->route('posts.show', $post);
}
4. Cache Versioning
Use version keys for instant invalidation:
// Get current cache version
$version = Cache::remember('posts:version', 86400, function () {
return time();
});
// Use version in cache key
$posts = Cache::remember("posts:all:v{$version}", 3600, function () {
return Post::all();
});
// Invalidate all versioned caches
public function clearPostsCache()
{
Cache::forget('posts:version');
// Next request will use new version, effectively clearing cache
}
CDN and Browser Caching
1. Asset Versioning
Use Laravel Mix/Vite for asset versioning:
// vite.config.js
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
build: {
rollupOptions: {
output: {
entryFileNames: 'js/[name].[hash].js',
chunkFileNames: 'js/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
},
},
},
});
2. Static Asset Caching
Configure in Nginx:
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
3. CDN Integration
Configure asset URL:
ASSET_URL=https://cdn.yourdomain.com
In config/app.php:
'asset_url' => env('ASSET_URL'),
Monitoring Cache Performance
1. Cache Hit Rate
Monitor cache effectiveness:
// Custom middleware
public function handle($request, Closure $next)
{
$key = 'cache_stats';
$stats = Cache::get($key, ['hits' => 0, 'misses' => 0]);
if (Cache::has($request->fullUrl())) {
$stats['hits']++;
} else {
$stats['misses']++;
}
Cache::put($key, $stats, 3600);
return $next($request);
}
2. Redis Monitoring
Monitor Redis performance:
# Connect to Redis
redis-cli
# Monitor cache statistics
INFO stats
# Watch commands in real-time
MONITOR
# Check memory usage
INFO memory
Conclusion
Mastering caching in Laravel 12 is essential for building high-performance applications. By implementing multi-layer caching strategies, using Redis effectively, optimizing responses, and managing cache invalidation properly, you can dramatically improve application speed and scalability.
Key takeaways:
- Use Redis for production caching
- Implement cache tags for organized invalidation
- Cache query results with proper TTL
- Use response caching for static content
- Implement proper cache invalidation strategies
- Monitor cache hit rates and optimize
- Configure browser and CDN caching
Need help implementing advanced caching strategies? NeedLaravelSite specializes in performance optimization and caching architecture. Contact us for expert Laravel development services.
Related Resources: