Laravel 13 Page Caching: Cut Your Blog TTFB Below 200 ms
A Laravel blog without page caching answers in 800 ms+ of TTFB; with the right setup it drops below 200 ms without touching your business logic. This guide walks you through the full combo: response caching, Nginx FastCGI, config cache, and Octane.
What TTFB Is and Why It Matters in 2026
Time To First Byte (TTFB) measures the time between the browser's request and the first byte of the server's response. It is the first signal Google sees about your site's speed: if the server is slow, everything else (LCP, INP) starts at a disadvantage.
Google's Core Web Vitals Thresholds
Core Web Vitals evaluates TTFB at the 75th percentile of real users. Up to 800 ms is considered "good", 800-1800 ms is "needs improvement", and anything above 1800 ms is "poor". The detail most people miss: 800 ms is the floor, not the goal. To keep LCP comfortable on mobile, aim for under 200 ms.
Why a Blog Is the Perfect Candidate for Caching
A blog has the ideal caching profile: GET routes, public pages, few changes, and no per-user state. The HTML of an article is identical for every visitor, so regenerating it on each request is pure waste. That is exactly where full-page caching delivers the most with the least effort.
Measure First: Diagnose Before You Optimize
Never optimize blind. Measure the real TTFB of your most visited routes and record the values before changing anything.
curl -o /dev/null -s -w "ttfb=%{time_starttransfer}s total=%{time_total}s\n" https://your-blog.com/articleRun the request several times and keep the p75, not the best case. Chrome DevTools (Network tab) and PageSpeed Insights show the same metric from the browser's perspective. Once you have the number, you know whether the bottleneck is the server, the database, or the application bootstrap.
Full-Page Caching with spatie/laravel-responsecache
The Spatie package is the fastest way to cache entire pages in Laravel: the first request runs the controller and stores the HTML; subsequent requests are served from cache without touching your logic.
Installation and Basic Setup
composer require spatie/laravel-responsecache
php artisan vendor:publish --provider="Spatie\ResponseCache\ResponseCacheServiceProvider"By default it caches GET responses with a 200 status. In the published config you can exclude routes, limit by URI, or set the TTL. For a blog, the default 24-hour lifetime is a solid starting point.
Invalidating the Cache When You Publish
The critical part: when you publish or edit an article, you must invalidate the cache so visitors see the change. The package ships helpers, and you can also call it from your publishing code:
use Spatie\ResponseCache\Facades\ResponseCache;
ResponseCache::clear(); // invalidates the whole response cacheIf you publish from an admin panel or an artisan command, call ResponseCache::clear() right after saving the post. Alternatively, cache only what changes rarely and keep the homepage out of it.
Flexible Caching in v8
Version 8 added "flexible caching": rules per URI and headers that let you decide what gets cached and for how long. For example, you can cache articles with a long TTL and listing pages with a short one, or exclude requests based on headers you do not want to store.
The Server-Side Alternative: Nginx FastCGI Cache
If you prefer to avoid dependencies or want an extra layer, Nginx can cache the full response at the server level. The fastcgi_cache directive stores the HTML and serves it without even starting PHP:
fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=laravel:10m max_size=1g inactive=60m;
location ~ \.php$ {
fastcgi_cache laravel;
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $http_authorization;
add_header X-FastCGI-Cache $upstream_cache_status;
}With the X-FastCGI-Cache header you can see in the response whether it came from cache (HIT) or was generated (MISS). It is a solid option when you do not want the cache living inside the application.
The Rest of the Combo: Config Cache, Queries, and Octane
Page caching attacks the biggest cost, but it is not the only piece. Laravel's bootstrap and heavy queries also add milliseconds.
php artisan optimize (config, route, view, event)
php artisan optimizeThis command compiles configuration, routes, views, and events into cached files. In production it stops Laravel from reading and parsing every file on each request. It is a one-line change that usually shaves tens of milliseconds off TTFB.
Cache::remember for Heavy Queries
Queries that repeat on every visit (menus, related articles, counters) deserve fragment caching:
use Illuminate\Support\Facades\Cache;
$posts = Cache::remember('latest_posts', 3600, function () {
return Post::published()->latest()->take(10)->get();
});The file store works from day one; if the site scales, switch to Redis by only touching config/cache.php.
Laravel Octane: No Per-Request Bootstrap
Octane (with Swoole, RoadRunner, or FrankenPHP) keeps the application in memory with long-lived workers. Laravel's bootstrap — which runs on every request under PHP-FPM — happens only once, removing the biggest TTFB hit after page caching.
php artisan octane:start --server=frankenphp --port=8000Combine Octane with response caching and you get the ideal scenario: cached pages never even reach PHP, and generated pages run without a cold start.
Expected Results and How to Measure Them
With the full combo, a blog that answered in about 900 ms of TTFB usually drops to the 180-250 ms range. Exact numbers depend on hosting, but the order of magnitude repeats in practice. Run the same curl -w command as before and compare: if TTFB dropped and LCP in PageSpeed Insights improved, the optimization worked.
Conclusion
Page caching in Laravel 13 is the most cost-effective tool to speed up a blog: install responsecache, invalidate on publish, add Nginx FastCGI if you want an extra layer, and finish with optimize, Cache::remember, and Octane. Measure before and after, and a TTFB below 200 ms stops being a goal and becomes your new normal. Keep reading the blog for more Laravel and web performance guides.