Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Caching Strategies: A Practical Guide

By Kokil Thapa | Last reviewed: September 2026

Your app feels fast in staging and crawls under real traffic. The fix is rarely a bigger server. Caching Strategies: A Practical Guide starts with one question: where does repeated work happen, and who should store the result? On production Laravel apps I maintain, the answer spans browser caches, CDN edges, reverse proxies, Redis, and query memoisation. This page maps those layers, shows copy-paste patterns, and covers invalidation without the usual hand-waving. If you already ship sites, pair this with our web performance caching guide for measurement context.

What Are the Main Caching Layers in a Modern Web Stack?

Every request passes through several decision points before PHP runs. Each layer can answer without hitting your app. That is the whole point of layered caching.

Start at the browser. Static assets with long Cache-Control headers load instantly on repeat visits. A CDN sits in front of your origin and stores HTML fragments, images, and API responses at edge nodes. Nginx or Varnish can cache full pages or micro-cache hot URLs. Inside Laravel, Redis holds config, routes, views, sessions, and computed aggregates. MySQL query cache is gone, but application-level query result caching still cuts database load sharply.

Web Caching Layer StackBrowser Cache — static assets, local storageCDN Edge — HTML, images, API at PoPReverse Proxy — Nginx / Varnish micro-cacheApplication — Redis, Laravel cache storeDatabase — query result cache, read replicas
Caching Strategies: A Practical Guide — five layers from browser to database, each with different TTL and invalidation rules

Browser and CDN caching

Public assets belong on long TTLs. Fingerprinted files from Vite 8.x can use Cache-Control: public, max-age=31536000, immutable. HTML and JSON usually need shorter TTLs or no-cache with revalidation. CDNs honour the same headers your origin sends. Misconfigured headers are a common cause of stale content or zero cache hit rate.

Reverse proxy and application cache

Nginx proxy_cache or Varnish can serve anonymous page shells in milliseconds. See our Varnish reverse-proxy caching guide for VCL patterns. Laravel talks to Redis 8.10 through the cache facade. That is where config, route, view, and data caches live in production.

How Do You Choose the Right Caching Strategy for Each Workload?

Not everything should be cached. The decision tree is simple once you classify the data.

  • Immutable reference data — countries, tax rates, category trees. Long TTL, tag invalidation on admin update.
  • Aggregates and dashboards — order counts, report summaries. Short TTL (60–300 seconds) or event-driven refresh.
  • User-specific views — cart, notifications, portal dashboards. Cache per user ID with narrow keys; purge on write.
  • Search and filters — cache keyed by query hash; invalidate when underlying records change.
  • Never cache — payment callbacks, CSRF tokens, one-time codes, personalised legal documents.
LayerBest forTypical TTLInvalidation
BrowserCSS, JS, fonts, images1 year (hashed)New build hash
CDNPublic HTML, API GET5 min – 24 hrCache purge API
Reverse proxyAnonymous pages30 sec – 5 minPurge + TTL expiry
Redis (app)Config, queries, counts1 min – 1 hrTags, events, TTL
DatabaseHeavy read reportsSession / materialisedRefresh job

On booking systems like Adventure Third Pole Trek, availability slots change often. A 30-second Redis cache beats a five-minute CDN cache for inventory pages. Marketing landing pages on the same site can sit on the CDN for hours.

Cache Strategy Decision TreeIs data user-specific?YesApp cache onlyKey: user:{id}:resourceNoDoes it change often?Yes: short TTL / eventsCDN + proxy OKLong TTL, purge on deployAuth, payments, tokens → never cache at edgeUse Cache-Control: private, no-store
Choose cache layer by personalization and mutation rate — the core decision in any caching strategy

How Should You Implement Application-Level Caching in Laravel?

Laravel 13.x (PHP 8.3+) ships with a unified cache API. Point CACHE_STORE=redis in .env and run Redis 8.10 on the same VPC as your app servers. I've used this stack on legal-tech portals and eCommerce builds alike.

Framework caches you should enable in production

  1. Config cachephp artisan config:cache after deploy. Cuts bootstrap I/O on every request.
  2. Route cachephp artisan route:cache when routes are static. Skip if you rely on closure routes.
  3. View cachephp artisan view:cache precompiles Blade templates.
  4. Event cachephp artisan event:cache on Laravel 11+ for faster listener resolution.

These belong in your Deployer 7 post-deploy hook. Reload PHP-FPM after symlink swap so opcache picks up changes. Our Laravel config, route, and view caching guide walks through each command.

Data caching with remember() and tags

Wrap expensive queries in Cache::remember(). Use tags when your Redis driver supports them:

use Illuminate\Support\Facades\Cache;

$categories = Cache::tags(['catalog'])->remember(
    'categories:tree',
    now()->addHour(),
    fn () => Category::with('children')->whereNull('parent_id')->get()
);

// On admin save:
Cache::tags(['catalog'])->flush();

For WooCommerce 11.1 stores or custom Laravel carts, cache product lists separately from stock counts. Stock changes more often than titles or descriptions. Split keys keep hit rates high without serving wrong inventory.

Locking prevents cache stampedes. Laravel's Cache::lock() lets one worker rebuild while others wait:

$data = Cache::remember('report:daily', 300, function () {
    return Cache::lock('report:daily:lock', 10)->block(5, function () {
        return DB::table('orders')->selectRaw('DATE(created_at), COUNT(*)')
            ->groupByRaw('DATE(created_at)')->get();
    });
});

Refer to the official Laravel 13.x cache documentation for driver configuration and atomic locks.

What Cache Invalidation Patterns Actually Work in Production?

Cache invalidation is the hard part. TTL-only strategies are fine for low-stakes data. Business-critical pages need explicit purge logic.

Event-driven invalidation

Fire a domain event when a model changes. A listener flushes tagged keys or deletes specific entries. On a production Laravel application, I wire ProductUpdated to Cache::tags(['product:'.$id])->flush(). That beats guessing TTL lengths.

Write-through and cache-aside

Cache-aside is the default Laravel pattern: read cache, on miss query DB and store. Write-through updates cache and DB together on every write. Use write-through for hot counters where stale reads hurt revenue. Use cache-aside everywhere else.

Cache-Aside vs Write-ThroughCache-Aside (read)AppRedisMySQL1 GET2 miss3 storeWrite-ThroughAppRedisMySQL (same txn)Invalidation triggersModel events · deploy hook · manual artisan cache:clearCDN purge API · tag flush · TTL expiryNever rely on TTL alone for legal or payment data
Cache-aside on reads and write-through on critical updates — two core invalidation-friendly patterns

HTTP cache headers for edge layers

Set headers in middleware or at the web server. Public blog posts can use:

return response($html)->header('Cache-Control', 'public, max-age=300, s-maxage=600')
    ->header('Vary', 'Accept-Encoding');

Authenticated portal pages need Cache-Control: private, no-store. The MDN Cache-Control reference documents every directive. Pair header strategy with our reverse-proxy caching setup guide.

Database query caching deserves its own treatment. Read database query caching strategies for Eloquent-specific patterns and N+1 avoidance alongside Redis memoisation.

How Do You Measure, Debug, and Avoid Common Caching Mistakes?

If you cannot measure hit rate, you are guessing. Add logging around cache misses in staging first. Redis INFO stats shows keyspace hits and misses globally.

Metrics that matter

  • Hit ratio — target 85%+ for reference data keys; 50–70% is normal for search caches.
  • Origin load — PHP-FPM active processes should drop after warm-up.
  • p95 latency — compare cached vs uncached routes in your APM.
  • Stale incident count — track support tickets tied to old content.

Use the JSON formatter tool to inspect cached API payloads during debugging. Export a key from Redis with redis-cli GET your:key and validate structure before blaming the database.

Production Cache LifecycleDeployGitLab CIWarm cacheartisan + HTTPTrafficCDN + RedisMonitorAPM + RedisStale data detected?Tag flush → CDN purge → dep rollback if neededCommon gotchasCaching auth pages · wrong Vary header · no lock on stampedeForgot config:cache after .env change · opcache not reloaded
Deploy, warm, monitor, and rollback — the production lifecycle for caching strategies that survive real traffic

Mistakes I see repeatedly

Caching the entire HTML output for logged-in users without Vary: Cookie leaks one user's dashboard to another. That is a security bug, not a performance win.

Running php artisan cache:clear in production during peak hours evicts every key at once. Prefer tag flushes scoped to the changed resource.

Storing session data in Redis while also using Redis for cache without separate databases (REDIS_CACHE_DB=1) causes accidental session loss on flush.

High-traffic sites need tiered TTLs. Our high-traffic caching strategies article covers stampede protection and CDN tiering in detail.

Redis data structures beyond plain strings — hashes, sorted sets, HyperLogLog — can replace heavy SQL for leaderboards and unique counts. See Redis caching and data structures for patterns that cut query time without denormalising every table.

CI pipelines benefit from layer caching too. Docker and Composer caches in GitLab CI shave minutes off each build. The CI build caching guide applies the same principles upstream of production.

When cache corruption or bad deploys cause widespread stale content, know your rollback path. Infrastructure rollback strategies pair with dep rollback on Deployer-managed hosts.

API rate limiting often shares Redis with application cache. Read rate limiting strategies for APIs to isolate keys and avoid contention on the same instance.

For WooCommerce-heavy builds like Sagun Blossom Flower, page caching plugins help anonymous traffic. Personalised cart and checkout paths still bypass the cache entirely. Know which plugin toggles affect which routes before enabling full-page cache in production.

Server tuning matters as much as application code. PHP-FPM worker count, Redis maxmemory-policy, and MySQL 9.7 buffer pool size all affect how much headroom you gain from caching. Our Linux system administration service covers Redis and PHP-FPM sizing on Ubuntu 22/24 hosts.

Professional audits catch misconfigured headers and missing warm-up scripts. If you want an outside review, see testing and optimization services or dedicated speed optimization for Core Web Vitals work alongside cache tuning.

The official Redis CLI documentation is the reference for inspecting keys, memory usage, and eviction stats during incidents.

Key Takeaways

  • Stack caches bottom-up: browser, CDN, reverse proxy, Redis, then query memoisation — each layer needs its own TTL and purge rules.
  • Enable Laravel config, route, view, and event caches on every production deploy; reload PHP-FPM after symlink swap.
  • Use tagged invalidation and domain events instead of global cache:clear during business hours.
  • Never cache authenticated HTML at the CDN without correct Vary headers; use private, no-store for portals and checkout.
  • Measure hit ratio and p95 latency per route; warm critical keys after deploy before sending traffic.
  • Separate Redis databases for cache, session, and queue to prevent accidental cross-flush.

People Also Ask

What is the difference between cache-aside and write-through caching?

Cache-aside loads data into the cache only on a miss. The application reads Redis first, then the database. Write-through updates both cache and database on every write. Cache-aside is simpler and fits most Laravel read-heavy pages. Write-through suits counters and inventory where stale reads have direct business cost.

How long should cache TTL be?

Match TTL to acceptable staleness. Static assets can live a year with content hashes. Public HTML often uses 5–15 minutes at the CDN. Application aggregates commonly use 60–300 seconds. User-specific data should expire quickly or invalidate on write rather than relying on a long TTL.

Is Redis required for Laravel caching?

No. Laravel supports file, database, Memcached 1.6.x, and array drivers. Redis is the production default because it is fast, supports tags on recent drivers, and doubles as session and queue storage. File cache works on small shared hosts but does not scale across multiple app servers.

How do you clear CDN cache after a deployment?

Call your CDN provider's purge API from the deploy pipeline. Purge changed URL paths or cache tags rather than full-zone flush when possible. Warm critical URLs with HTTP requests immediately after purge so the first real user does not pay the cold-cache penalty.

Ship Caching That Survives Production Traffic

Caching Strategies: A Practical Guide is not a one-time config change. It is a lifecycle: classify data, pick the right layer, invalidate on events, measure hit rate, and roll back when something goes wrong. Start with Laravel framework caches and Redis for your heaviest queries. Add CDN and reverse-proxy rules only after HTTP headers are correct.

If you want help auditing an existing app or designing cache architecture for a new build, review our web development services or enterprise application development offerings. For ongoing tuning after launch, support and maintenance keeps deploy hooks, Redis memory, and CDN purges aligned with your traffic patterns.

Contact us with your stack details — Laravel version, traffic profile, current Redis setup — and we can map a caching plan that fits your budget and team size.

Frequently Asked Questions

Every request passes through several decision points before PHP runs. The article maps five layers: browser caches honouring Cache-Control on static assets; CDN edges storing HTML fragments, images, and API responses; Nginx or Varnish reverse proxies serving anonymous page shells; Laravel application cache in Redis 8.10 for config, routes, views, sessions, and computed aggregates; and application-level query memoisation replacing the removed MySQL query cache. Each layer can answer without hitting your origin, but each needs its own TTL and invalidation rules. Stack them bottom-up rather than relying on one store for everything.

Classify data before picking a layer. Immutable reference data such as countries, tax rates, and category trees suit long TTLs with tag invalidation on admin update. Aggregates and dashboards need short TTLs of sixty to three hundred seconds or event-driven refresh. User-specific views such as carts and portal dashboards should cache per user ID and purge on write. Search results cache by query hash and invalidate when underlying records change. Never cache payment callbacks, CSRF tokens, one-time codes, or personalised legal documents. On booking systems like Adventure Third Pole Trek, availability slots need a thirty-second Redis cache, while marketing landing pages can sit on the CDN for hours.

Laravel 13.x on PHP 8.3 or higher provides a unified cache API. Set CACHE_STORE=redis in .env and run Redis 8.10 on the same VPC as your app servers. Wrap expensive queries in Cache::remember() and use tags when your Redis driver supports them, flushing tags such as catalog on admin save. For WooCommerce 11.1 stores or custom Laravel carts, cache product lists separately from stock counts because stock changes more often than titles. Use Cache::lock() to prevent stampedes so one worker rebuilds while others wait. Add config, route, view, and event caching to your Deployer 7 post-deploy hook and reload PHP-FPM after the symlink swap.

TTL-only strategies are fine for low-stakes data, but business-critical pages need explicit purge logic. Event-driven invalidation fires a domain event when a model changes; a listener flushes tagged keys or deletes specific entries, which beats guessing TTL lengths. Cache-aside is the default Laravel pattern: read cache, query the database on miss, then store. Write-through updates cache and database together on every write and suits hot counters where stale reads hurt revenue. For edge layers, set HTTP cache headers in middleware or at the web server. Public blog posts can use public max-age with s-maxage for CDN tiers, while authenticated portal pages need Cache-Control private and no-store.

Cache-aside fills cache on read miss from the database. Write-through updates cache and database on every write. Default to cache-aside for read-heavy pages; use write-through when stale reads hurt revenue.

Match TTL to acceptable staleness: one year for hashed static assets, five to fifteen minutes for public HTML at the CDN, sixty to three hundred seconds for application aggregates, and short TTL or write-time invalidation for user-specific data.

No. Laravel supports file, database, Memcached 1.6.x, and array drivers. Redis 8.10 is the usual production choice for speed, tag support, and shared use for sessions and queues.

Run php artisan config:cache after deploy to cut bootstrap I/O on every request. Run php artisan route:cache when routes are static, skipping it if you rely on closure routes. Run php artisan view:cache to precompile Blade templates. On Laravel 11 and later, run php artisan event:cache for faster listener resolution. These commands belong in your Deployer 7 post-deploy hook alongside the symlink swap. Reload PHP-FPM after deploy so opcache picks up changes. Treat these framework caches as baseline production hygiene before adding custom Redis data caching for heavy queries.

A cache stampede happens when many concurrent requests miss the same key and all hammer the database at once. Laravel's Cache::lock() lets one worker rebuild the expensive result while others block and wait. Wrap the rebuild logic inside Cache::remember() with a lock key and a short block timeout. This pattern matters for daily report aggregates, dashboard counts, and any query that takes seconds uncached. Pair locking with sensible TTLs so keys expire predictably rather than all at once. The article also points to tiered TTLs and high-traffic caching strategies for additional stampede protection at CDN and reverse-proxy layers.

If you cannot measure hit rate, you are guessing. Redis INFO stats shows keyspace hits and misses globally. Target eighty-five percent or higher hit ratio for reference data keys; fifty to seventy percent is normal for search caches. Watch origin load: PHP-FPM active processes should drop after warm-up. Compare p95 latency on cached versus uncached routes in your APM. Track stale incident count through support tickets tied to old content. In staging, add logging around cache misses first. Export a key with redis-cli GET your:key and validate structure before blaming the database. Follow deploy, warm, monitor, and rollback as the production lifecycle for caching that survives real traffic.

Caching entire HTML output for logged-in users without Vary Cookie leaks one user's dashboard to another. That is a security bug, not a performance win. Running php artisan cache:clear in production during peak hours evicts every key at once; prefer tag flushes scoped to the changed resource. Storing session data in Redis while also using Redis for cache without separate databases causes accidental session loss on flush. Set REDIS_CACHE_DB=1 to isolate cache from sessions and queues. Misconfigured Cache-Control headers cause stale public content or zero CDN hit rate. High-traffic sites need tiered TTLs rather than one global expiry for every key type.

Only with strict rules. Authenticated portal pages need Cache-Control private and no-store so intermediaries never store personalised responses. Caching full HTML for logged-in users at the CDN without correct Vary headers has caused session leakage in production. User-specific views such as carts, notifications, and portal dashboards can use Redis keyed by user ID with narrow keys and purge on write, but edge layers should not serve that HTML to other visitors. Never cache payment callbacks, CSRF tokens, one-time codes, or personalised legal documents. For WooCommerce builds like Sagun Blossom Flower, anonymous page caching helps traffic while cart and checkout paths bypass cache entirely.

Public assets from Vite 8.x with content hashes can use Cache-Control public, max-age=31536000, immutable because a new build changes the filename. HTML and JSON usually need shorter TTLs or no-cache with revalidation. Public blog posts can use public max-age=300 with s-maxage=600 so the CDN holds longer than the browser. Always include Vary Accept-Encoding when compression differs by client. CDNs honour the same headers your origin sends, so misconfigured headers are a common cause of stale content or zero cache hit rate. Pair header strategy with reverse-proxy caching rules only after origin headers are correct.

Call your CDN provider's purge API from the deploy pipeline rather than flushing manually during peak hours. Purge changed URL paths or cache tags rather than full-zone flush when possible. Immediately warm critical URLs with HTTP requests after purge so the first real user does not pay the cold-cache penalty. The article treats this as part of the deploy, warm, monitor, and rollback lifecycle. When bad deploys cause widespread stale content, pair CDN purge with infrastructure rollback strategies and dep rollback on Deployer-managed hosts. Avoid relying on TTL expiry alone for business-critical pages that changed in the release.

On production Laravel applications, Redis 8.10 often serves triple duty for cache, sessions, and queues. Without separation, a scoped cache flush or accidental cache:clear can wipe active sessions and drop queued jobs. Set REDIS_CACHE_DB=1 so cache keys live on a different logical database from session and queue data. API rate limiting also shares Redis with application cache on many stacks, so isolate keys and avoid contention on the same instance. This is a recurring production gotcha I have seen after deploys that looked fine in staging but logged users out under load. Separate databases cost nothing and prevent cross-flush surprises.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: