Skip to main content

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.

SitePerf scoreLCP (lab)TBTCLS
laravel.com40/1004.6 s3429 ms0
laracasts.com89/1002.7 s268 ms0

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.

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.

Enter a public website address, including https://. No account is needed to start a trial test.