Optimizing TTFB (Time to First Byte)
TTFB — time from request sending to receiving first byte from server. Good value: ≤ 800 ms (Google considers up to 600 ms acceptable). TTFB directly affects LCP: impossible to have good LCP with poor TTFB.
What comprises TTFB
DNS → TCP → TLS → Request → Server processing → First byte of response
Typical breakdown:
- DNS lookup: 10–100 ms (solved via preconnect + CDN)
- TCP + TLS: 30–150 ms (solved via HTTP/2, CDN)
- Server processing: 50–3000+ ms (most manageable part)
Full Page Cache
Most effective method: cache ready HTML.
// spatie/laravel-responsecache or custom solution
// Middleware added for guest GET requests
class FullPageCache
{
private const TTL = 300; // 5 minutes
public function handle(Request $request, Closure $next): Response
{
if (!$this->isCacheable($request)) {
return $next($request);
}
$key = $this->cacheKey($request);
if (Cache::has($key)) {
return response(Cache::get($key))
->header('X-Cache', 'HIT')
->header('Content-Type', 'text/html; charset=UTF-8');
}
$response = $next($request);
if ($response->getStatusCode() === 200) {
Cache::put($key, $response->getContent(), self::TTL);
}
return $response->header('X-Cache', 'MISS');
}
private function isCacheable(Request $request): bool
{
return $request->isMethod('GET')
&& !auth()->check()
&& !$request->hasCookie(session()->getName());
}
private function cacheKey(Request $request): string
{
return 'fpc:' . sha1($request->fullUrl());
}
}
Nginx FastCGI Cache
Cache at nginx level — faster than PHP/Redis cache:
# nginx.conf
fastcgi_cache_path /var/cache/nginx/fcgi
levels=1:2
keys_zone=LARAVEL:10m
inactive=60m
max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
location ~ \.php$ {
fastcgi_cache LARAVEL;
fastcgi_cache_valid 200 5m;
fastcgi_cache_valid 404 1m;
# Don't cache for authorized users
fastcgi_cache_bypass $cookie_laravel_session $http_authorization;
fastcgi_no_cache $cookie_laravel_session $http_authorization;
add_header X-Fastcgi-Cache $upstream_cache_status;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
}
Purge by tag (via nginx-cache-purge module):
curl -X PURGE https://example.ru/products/iphone-15-pro
Database query optimization
// Slow query: N+1 problem
$products = Product::all();
foreach ($products as $product) {
echo $product->category->name; // N+1 queries to categories
}
// Fast: eager loading
$products = Product::with(['category', 'images', 'brand'])->get();
// Profiling slow queries
DB::listen(function ($query) {
if ($query->time > 100) {
Log::warning('Slow query', [
'sql' => $query->sql,
'time' => $query->time,
]);
}
});
Redis for data caching
// Cache heavy query results
$categories = Cache::remember('categories:tree', 3600, function () {
return Category::with('children')
->whereNull('parent_id')
->orderBy('sort_order')
->get();
});
// Cache counters
$stats = Cache::remember('product:stats:' . $productId, 300, function () use ($productId) {
return [
'views' => ProductView::where('product_id', $productId)->count(),
'sales' => OrderItem::where('product_id', $productId)->sum('quantity'),
'wishlist' => WishlistItem::where('product_id', $productId)->count(),
];
});
DNS and network optimizations
<!-- Preconnect to critical resources -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.ru" crossorigin>
<link rel="dns-prefetch" href="https://analytics.google.com">
OPcache for PHP
; php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; 0 in production
opcache.jit=tracing
opcache.jit_buffer_size=64M
Target values by request type
| Page type | With cache | Without cache |
|---|---|---|
| Homepage | < 50 ms | < 300 ms |
| Catalog | < 50 ms | < 400 ms |
| Product card | < 50 ms | < 200 ms |
| API endpoints | — | < 100 ms |
Optimization time: 2–3 days for cache setup and query profiling.







