The Dev Log › Technology
Web Performance in 2026: Core Web Vitals Fixes That Actually Move the Needle
By Jezer Niel Blanca, Full Stack Developer ·
·
6 min read
LCP, INP and CLS in plain English, how to measure them with real-user data, and the fixes that make a real difference on Laravel and Vue apps.
Performance is one of those things everyone agrees matters and almost nobody measures until something feels slow. Google's Core Web Vitals give us a shared language for it: how fast the main content appears, how quickly the page responds to input, and how stable the layout is while it loads. In this post I'll go through the three metrics, how I measure them, and the fixes that make a real difference on the Laravel and Vue apps I build, as opposed to the micro-optimizations that look good in a checklist and change nothing.
The Three Metrics in Plain English
Core Web Vitals boil down to three questions a user is silently asking:
- Largest Contentful Paint (LCP): "When can I see the main thing?" It measures how long the largest visible element, usually a hero image or headline, takes to render.
- Interaction to Next Paint (INP): "Does it respond when I tap?" It measures the delay between user interactions and the next visual update, across the whole visit. INP replaced First Input Delay as a Core Web Vital in 2024.
- Cumulative Layout Shift (CLS): "Is the page jumping around?" It measures how much visible content moves unexpectedly while the page loads.
Google publishes "good" thresholds for each metric, assessed at the 75th percentile of real visits. The key phrase there is real visits. Your fast laptop on office Wi-Fi is not your users' experience.
Measure Before You Fix
There are two kinds of performance data, and you need both:
- Lab data comes from tools like Lighthouse and the Chrome DevTools Performance panel. It's repeatable and great for debugging.
- Field data comes from real users, either through the Chrome UX Report that powers PageSpeed Insights or from your own monitoring.
Collecting your own field data is easy with the small web-vitals library:
npm install web-vitals
import { onCLS, onINP, onLCP } from 'web-vitals'
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
page: window.location.pathname,
})
navigator.sendBeacon('/vitals', body)
}
onCLS(sendToAnalytics)
onINP(sendToAnalytics)
onLCP(sendToAnalytics)
Point that at a tiny Laravel endpoint that stores the values, and within days you'll know which pages actually need attention.
Optimize the pages your users visit on the devices they actually use. Everything else is guesswork.
Fixing LCP: Get the Main Content on Screen Sooner
LCP is usually made of two parts: how long the server takes to respond, and how long the largest element takes to load and render after that.
Speed Up the Server Response
A slow Time to First Byte delays everything. On Laravel apps, the usual suspects are:
- N+1 queries on the page's main listing. Eager load relationships with
with().
- Expensive queries that rarely change. Cache them:
$featured = Cache::remember('home.featured-projects', now()->addMinutes(30), function () {
return Project::query()->where('is_featured', true)->with('tags')->latest()->limit(6)->get();
});
- Missing production optimizations. Run
php artisan optimize during deployment so config, routes, events and views are cached.
Prioritize the Hero Image
If the LCP element is an image, make sure the browser discovers and fetches it early, and never lazy-load it:
<template>
<img
src="/images/hero.webp"
alt="Dashboard preview"
width="1200"
height="630"
fetchpriority="high"
class="h-auto w-full rounded-2xl"
/>
</template>
Modern formats like WebP or AVIF, served at a sensible size for the viewport, are often the single biggest LCP improvement on image-heavy pages.
Fixing INP: Keep the Main Thread Free
INP suffers when JavaScript blocks the main thread, so the browser can't respond to a click until some long task finishes. The fixes are about doing less work, or doing it later.
- Ship less JavaScript on first load. Load heavy components only when needed. In Vue,
defineAsyncComponent makes that a one-liner:
import { defineAsyncComponent } from 'vue'
const RevenueChart = defineAsyncComponent(() => import('@/Components/RevenueChart.vue'))
- Break up long tasks. If a click handler does a lot of work, update the UI first and yield before continuing:
async function applyFilters() {
isLoading.value = true
await new Promise((resolve) => setTimeout(resolve, 0))
results.value = expensiveFilter(allItems.value, filters.value)
isLoading.value = false
}
- Audit third-party scripts. Chat widgets, tag managers and trackers often cost more main-thread time than your own code. Load them with
defer, delay them until after user interaction, or question whether you need them at all.
- Debounce input handlers for search boxes and filters so you're not re-rendering on every keystroke.
Fixing CLS: Reserve Space for Everything
Layout shifts happen when content appears without space reserved for it. The fixes are refreshingly mechanical:
- Always set
width and height on images and videos, or use an aspect ratio utility like aspect-video in Tailwind, so the browser reserves space before the file loads.
- Give dynamic content a placeholder with the same dimensions. Skeleton loaders for deferred data do double duty here: they look good and they prevent shifts.
- Don't inject banners above existing content. Cookie notices and promo bars should overlay the page or have space reserved from the start.
- Load fonts carefully. Use
font-display: swap and, where possible, a fallback font with similar metrics, so text doesn't reflow dramatically when the web font arrives.
The Boring Wins That Help Everything
A few server-level settings improve all three metrics at once:
- Compression with Brotli or gzip for text assets.
- Long cache lifetimes for fingerprinted assets. Vite adds content hashes to build filenames, so they can be cached aggressively.
- HTTP/2 or HTTP/3, which most modern hosts enable by default.
- A CDN for static files if your users are spread across regions.
Wrapping up
Core Web Vitals reward the fundamentals: a fast server response, a prioritized hero element, less JavaScript on the main thread, and reserved space for everything that loads late. Measure with real-user data, fix the worst pages first, and re-measure after every change so you know what actually worked. If you want a product that feels fast on real devices from day one, let's build it together with my team.
Tags: Performance, Core Web Vitals, Vue, Laravel