Home / guides / Laravel
Core Web Vitals for Laravel
Updated · by RankVitals
Laravel renders HTML on your server, which means Core Web Vitals start with TTFB — and TTFB is where unoptimized Laravel apps bleed: no response caching, N+1 queries, debug mode left on, cold framework boots. The fixes are unusually well-paved: artisan optimize, response caching, Octane, and Vite. This guide walks the Laravel-specific route from slow first byte to green vitals.
Real-world measurements, July 2026
Mobile lab results for well-known Laravel sites, measured with Lighthouse — the same engine behind Google PageSpeed Insights and RankVitals tests. Snapshot taken July 19, 2026 — your numbers will differ, which is the point: measure your own site.
| Site | Perf score | LCP (lab) | TBT | CLS |
|---|---|---|---|---|
| laravel.com | 40/100 | 4.6 s | 3429 ms | 0 |
| laracasts.com | 89/100 | 2.7 s | 268 ms | 0 |
Lab data: Lighthouse 13, emulated Moto G Power over simulated 4G — the identical methodology PageSpeed Insights uses for its lab section. Single runs; lab numbers vary a few percent between runs.
What real Laravel sites measure
We measured two flagship Laravel properties with Lighthouse 13 (mobile emulation) in July 2026. laravel.com scored 40/100 — LCP 4.6 s and a hefty 3,429 ms of Total Blocking Time, driven by the marketing site’s animation and script payload rather than by PHP; its server responds quickly. laracasts.com, a production Laravel + Inertia application, scored 89/100 with LCP at 2.7 s, TBT 268 ms, and CLS 0.00 — proof that a real, dynamic Laravel app can sit at the top of the range when the frontend is disciplined. The pair makes this guide’s point cleanly: Laravel’s server-side story — caching, Octane, query discipline — sets your TTFB floor, but whether you land at 40 or 89 is decided by what the Blade layout ships to the browser.
TTFB first: caching at every layer Laravel gives you
Before touching frontend metrics, get the server response under control — every millisecond of TTFB delays every paint metric behind it. Laravel ships the levers: php artisan optimize caches config, routes, events, and views into compiled files (run it on every deploy; config:cache alone saves parsing dozens of files per request). OPcache should be enabled and sized in production PHP — it is the single largest PHP-level win and costs one ini change.
Then cache responses, not just fragments. spatie/laravel-responsecache stores entire rendered responses and serves repeat requests in a few milliseconds without booting your controllers; for pages that are identical for all guests (marketing pages, blog posts, docs), pair it with a CDN like Cloudflare in front honoring Cache-Control headers, and most visitors never reach PHP at all. Scope it deliberately — skip authenticated routes or vary by session — and invalidate on model updates with the package’s cache-clearing hooks.
Octane and the query layer
Standard PHP-FPM boots the framework — container, providers, config — on every request. Laravel Octane keeps the application booted in memory using FrankenPHP, Swoole, or RoadRunner, and turns each request into a reuse of a warm app: framework overhead drops from tens of milliseconds to single digits. It is the highest-leverage infrastructure change for TTFB on dynamic pages that cannot be response-cached, at the cost of caring about memory leaks and stale singletons.
Underneath, the database usually decides the rest. N+1 queries are the classic Laravel tax: a Blade loop lazily loading a relation per row turns one query into two hundred. Find them with Laravel Debugbar or Telescope in development — the query panel makes them obvious — and fix with eager loading (with(), or load() on demand). Model::preventLazyLoading() in non-production environments turns every future N+1 into a loud exception instead of a silent slowdown. Push anything slow that isn’t needed for the response — mail, PDFs, external APIs, image processing — onto queues with Horizon managing workers.
Frontend assets: Vite, Livewire, and Inertia
Laravel’s Vite integration (laravel-vite-plugin, the @vite Blade directive) gives you hashed, minified, code-split bundles out of the box — in production it emits fingerprinted files you can cache forever. Keep the entry small: import page-specific JavaScript in page-specific entries rather than one app.js that grows with every feature. Tailwind’s build strips unused CSS automatically; if you hand-roll CSS, audit what ships.
The stack flavors matter. Livewire keeps rendering on the server — excellent for TBT since little JavaScript ships — and wire:navigate upgrades navigation to SPA-like speed with prefetching. Inertia ships a real client-side app; enable Inertia SSR so first paint is server-rendered HTML rather than a blank div waiting for the bundle, or LCP on slow connections will suffer. Either way, defer scripts that aren’t needed at paint, and load analytics after the page is interactive.
Images and static delivery
Laravel does nothing automatic about images, so pick your pipeline. spatie/laravel-medialibrary generates conversions (sizes and WebP variants) at upload time and stores them on S3 or local disks; intervention/image covers ad-hoc processing; or delegate to an image CDN and serve resize-on-the-fly URLs. Whatever the pipeline, output responsive markup from your Blade components — srcset, sizes, explicit width and height (for CLS), loading="lazy" below the fold, and an eagerly loaded, high-priority hero.
Static assets should not touch PHP: serve public/ directly via nginx with long-lived immutable Cache-Control headers (safe because Vite fingerprints filenames), enable Brotli or gzip at the web server, and front the whole origin with a CDN so assets and cached pages serve from the edge. Behind a proxy or load balancer, configure trusted proxies so Laravel generates https URLs — mixed-content from a misdetected scheme (asset() emitting http:// on an https page) is a classic Laravel-behind-a-load-balancer bug.
Measuring Laravel correctly
First, verify the app is actually in production mode: APP_ENV=production, APP_DEBUG=false, caches built. Debug mode alone can multiply TTFB, and measuring a staging box with debug on tells you nothing. Measure the production URL through its real CDN and proxy chain, cold and warm — the first request after a deploy pays cache-building costs that steady-state visitors never see.
Diagnose in two layers. Server timing: Debugbar and Telescope in a non-production copy show where milliseconds go (queries, views, external calls); clockwork or server-timing headers can expose the same in production carefully. Browser timing: once TTFB is flat, run the lab test and read the waterfall — remaining LCP problems on Laravel apps are almost always an unpreloaded hero image or render-blocking asset order, and TBT problems are almost always third-party scripts, because a server-rendered Blade or Livewire page ships very little JavaScript of its own.
What our audit checks on Laravel sites
These are real checks from the RankVitals SEO audit that most often fire on Laravel sites. Run the audit to see which ones apply to yours.
mixed_contentThe classic Laravel-behind-a-proxy bug: trusted proxies unset, so asset() and url() emit http:// URLs into an https page.custom_404_missingLaravel’s default 404 view is nearly empty — publish and design resources/views/errors/404.blade.php so dead links keep visitors on the site.sitemap_missingNothing generates a sitemap in a Laravel app until you add spatie/laravel-sitemap (or a route that builds one) — commonly forgotten on content sites.redirect_chainshttp → https and non-www → www handled in different layers (nginx, middleware, CDN) easily stack into multi-hop chains on Laravel deployments.duplicate_titlesA layout with a hardcoded <title> and views that never override the section produces one title across the whole app — a Blade-specific classic.
Frequently asked questions
What single change most improves Laravel TTFB?
For pages identical across guests: full response caching (spatie/laravel-responsecache or CDN page caching) — repeat requests skip the framework entirely. For genuinely dynamic pages: Laravel Octane, which keeps the app booted and removes per-request framework startup. Run php artisan optimize and enable OPcache in either case; they are prerequisites, not alternatives.
Octane or more servers — which should I choose?
Octane first. It multiplies throughput per server by removing framework boot from each request and typically cuts response times more than horizontal scaling would, at zero infrastructure cost. Scale out afterward if load still demands it — the two compose. Just test for state leaks: singletons persist between requests under Octane.
Do Livewire or Inertia hurt Core Web Vitals?
Livewire is naturally strong on TBT — rendering stays server-side and little JavaScript ships; use wire:navigate for fast subsequent pages. Inertia without SSR hurts LCP because first paint waits on the JavaScript bundle; enable Inertia SSR and it behaves like a server-rendered app with an SPA feel. Neither is a problem configured properly.
Why are my Core Web Vitals bad when my server responds in 50ms?
Then TTFB is not your problem — the frontend is. The usual Laravel-app culprits: an unoptimized hero image without preload or priority (LCP), render-blocking CSS/JS order in the Blade layout, missing width/height on images (CLS), and third-party scripts (TBT). Run a lab test and read the waterfall; the server work is done.
Measure your Laravel site now
Free speed test with waterfall + filmstrip, and an SEO audit in the same tool.