Caching Strategies for Web Apps: Developer Guide

Browser cache, CDN cache, application cache, database cache — 4 caching layers. We explain each and when to use it.
Why Is Caching Essential?
Every successful web application eventually hits the same challenge: traffic increases, but the database and server cannot keep up. The solution is not always upgrading specifications — it is usually implementing a well-designed caching strategy.
Good caching reduces resource consumption by 70-90% and improves response time from hundreds of milliseconds to under 10ms.
The Seven Levels of Caching
Level 1: Browser Cache
The browser stores files locally on the user's device.
# nginx: set browser cache duration
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(jpg|png|gif|webp)$ {
expires 6M;
add_header Cache-Control "public";
}
location = /index.html {
expires -1;
add_header Cache-Control "no-cache, no-store";
}
Level 2: CDN Cache
# Cloudflare reuses nginx Cache-Control headers above
# Or define them in Cloudflare Transform Rules
Level 3: Application Cache (Redis)
// Laravel: Full page cache
Route::get('/pricing', function {
return Cache::remember('page:pricing', 3600, function {
return Inertia::render('Pricing', [
'plans' => Plan::with('features')->get,
]);
});
});
// Or use HTTP Cache Headers
return response
->view('pricing')
->header('Cache-Control', 'public, max-age=3600');
Level 4: Query Cache
// Cache results of heavy queries
$stats = Cache::remember('dashboard:stats', 300, function {
return [
'total_users' => User::count,
'active_servers' => Server::where('status', 'active')->count,
'monthly_revenue' => Invoice::thisMonth->sum('amount'),
];
});
// Cache with Tags for selective clearing
Cache::tags(['products'])->remember("product:{$id}", 86400, function use ($id) {
return Product::with(['category', 'specs', 'pricing'])->find($id);
});
Level 5: Object Cache
// Pre-cache PHP objects
class UserRepository
{
public function find(int $id): User
{
return Cache::remember(
"user:{$id}",
1800,
fn => User::with('permissions', 'subscription')->findOrFail($id)
);
}
public function clearCache(int $id): void
{
Cache::forget("user:{$id}");
}
}
Level 6: Full-Page Cache
// Middleware for full-page caching
class CacheResponse
{
public function handle(Request $request, Closure $next): Response
{
// Do not cache POST requests or authenticated pages
if ($request->method !== 'GET' || $request->user) {
return $next($request);
}
$cacheKey = 'page:' . md5($request->url);
if (Cache::has($cacheKey)) {
return response(Cache::get($cacheKey));
}
$response = $next($request);
Cache::put($cacheKey, $response->getContent, 3600);
return $response;
}
}
Level 7: Computed/Memoization Cache
// Memoize results of heavy computations within a single request
class PricingCalculator
{
private array $memo = [];
public function calculate(array $config): float
{
$key = md5(serialize($config));
if (!isset($this->memo[$key])) {
$this->memo[$key] = $this->doHeavyCalculation($config);
}
return $this->memo[$key];
}
}
Cache Invalidation Strategies
1. TTL (Time-To-Live) — Simplest
// Data expires automatically after the duration
Cache::put('data', $value, 3600); // 1 hour
Cache::put('data', $value, now->addDays(7)); // 1 week
Pros: Simple, requires no extra code Cons: Stale data until TTL expires
2. Cache Tags — For Related Data
// Store
Cache::tags(['posts', "user:{$userId}"])->put(
"posts:user:{$userId}",
$posts,
3600
);
// Clear all cache for a specific user
Cache::tags(["user:{$userId}"])->flush;
// Clear all posts
Cache::tags(['posts'])->flush;
3. Event-Driven Invalidation — Most Precise
// When a product is updated, automatically clear cache
class Product extends Model
{
protected static function booted: void
{
static::saved(function (Product $product) {
Cache::forget("product:{$product->id}");
Cache::tags(['products'])->flush;
});
}
}
Cache Duration Selection Guide
| Data Type | Appropriate Cache Duration |
|---|---|
| Application settings | 24 hours or more |
| Product listings | 1-6 hours |
| User data | 15-30 minutes |
| Search results | 5-10 minutes |
| Dashboard statistics | 5 minutes |
| Session data | Session lifetime |
| External API data | 1 minute - 1 hour |
Caching on cloud.net.sa Servers
cloud.net.sa servers use high-speed NVMe SSDs, allowing Redis to operate at maximum efficiency. With the ability to add RAM at 600 SAR per 2GB, you can scale your cache easily as your application grows.
Recommendation: Dedicate at least 20% of RAM to Redis cache. For an 8GB RAM server, this means allocating 1.5-2GB to Redis.
# Adjust Redis maxmemory
redis-cli config set maxmemory 1500mb
redis-cli config set maxmemory-policy allkeys-lru
Common Caching Mistakes
Cache Stampede
// Problem: 1000 requests simultaneously when cache expires
// Solution: Cache Lock
$value = Cache::lock("rebuild:product:{$id}", 10)->get(function use ($id) {
return Cache::remember("product:{$id}", 3600, function use ($id) {
return Product::expensive->find($id);
});
});
Cache Poisoning
// Do not store user data in public cache
// Wrong:
Cache::put('homepage', view('home', ['user' => auth->user]));
// Correct:
if (!auth->check) {
Cache::put('homepage:guest', view('home'));
}
Summary
Good caching transforms a slow application into a fast one — often without touching the original code. Start with Query Cache, then Object Cache, and finally Full-Page Cache if needed.
Get Started
Ready to build your project? Configure your cloud server with the specs your app needs, or view plans and pricing.
