I've watched hundreds of PageSpeed reports scroll past in support tickets, and the pattern is always the same: a site owner stares at a red LCP score, tries a caching plugin, and sees almost no change. The real fixes are more specific than that.
Core Web Vitals measure three things Google cares about: how fast your largest content paints (LCP), how quickly the page responds to clicks and taps (INP), and whether elements jump around while loading (CLS). You can move the needle on all three with targeted changes, and the approach works whether you're running WordPress on shared hosting, a Next.js app on Vercel, or a static site behind Cloudflare.
What actually moves LCP scores
Largest Contentful Paint tracks the render time of the biggest visible element in the viewport—usually a hero image, a headline block, or a video thumbnail. If LCP is over 2.5 seconds, your server response time or image delivery is the bottleneck.
Start with TTFB (Time to First Byte). Run curl -w "\nTTFB: %{time_starttransfer}s\n" -o /dev/null -s https://yoursite.com from a terminal. If TTFB is above 600ms, your server is slow before the browser even starts painting. On shared hosting, that usually means PHP execution is grinding through an inefficient theme or twenty active plugins. On a VPS, check your database query time and whether opcache is enabled.
Once TTFB is acceptable, the next problem is usually image format and delivery. A 2MB JPEG hero image served without optimization will wreck LCP no matter how fast your server responds. Convert large images to WebP or AVIF, serve them through a CDN, and make sure fetchpriority="high" is set on the LCP element.
For WordPress sites, here's the fastest path:
# Install WebP Express or similar plugin via WP-CLI
wp plugin install webp-express --activate
# Enable opcache in php.ini if you have access
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
In your theme's header template, find the hero image or primary heading and add the fetch priority attribute:
<img src="hero.webp" alt="Main visual" fetchpriority="high" width="1200" height="600">
Width and height attributes are not optional. They prevent CLS and help the browser allocate space before the image loads.
For Next.js, use the Image component and set priority on above-the-fold images:
import Image from 'next/image'
export default function Hero() {
return (
<Image
src="/hero.webp"
alt="Main visual"
width={1200}
height={600}
priority
/>
)
}
The framework handles format negotiation and lazy loading automatically, but you still need to specify dimensions and mark the LCP candidate as priority.
Static sites behind Cloudflare benefit from Polish (image optimization) and early hints. Enable both in the dashboard under Speed > Optimization. Early hints send link preload headers before the HTML finishes generating, giving the browser a head start on fetching critical resources.
Fixing INP (the new FID)
Interaction to Next Paint replaced First Input Delay in 2024 and measures how long it takes the page to respond to all user interactions—clicks, taps, key presses—not just the first one. An INP below 200ms is good; anything over 500ms is a problem.
The usual culprit is JavaScript blocking the main thread. Check your PageSpeed report for long tasks. If you see a 600ms task labeled "Evaluate Script," a heavy JS bundle is locking up the browser.
Defer non-critical scripts. In WordPress, every theme and plugin loads its own JavaScript, often on every page even when not needed. Use a plugin like Asset CleanUp to disable scripts on pages where they don't belong, or add this to your theme's functions.php:
function defer_non_critical_js($tag, $handle) {
$defer = array('jquery-migrate', 'comment-reply', 'wp-embed');
if (in_array($handle, $defer)) {
return str_replace(' src', ' defer src', $tag);
}
return $tag;
}
add_filter('script_loader_tag', 'defer_non_critical_js', 10, 2);
That won't solve everything, but it keeps secondary scripts from blocking render.
For Next.js apps, dynamic imports are your friend. Split large components so they only load when needed:
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
loading: () => <p>Loading chart...</p>,
ssr: false
})
The ssr: false option skips server rendering for client-only interactive components, reducing the initial JS payload.
Another common INP killer is third-party embeds—analytics, chat widgets, social feeds. Load them with a slight delay using an intersection observer or a simple timeout:
setTimeout(() => {
const script = document.createElement('script');
script.src = 'https://example.com/widget.js';
document.body.appendChild(script);
}, 3000);
Three seconds gives the page time to become interactive before the widget loads.
Stopping layout shifts (CLS)
Cumulative Layout Shift measures how much visible content moves around during load. A score above 0.1 is poor, and it's almost always caused by images, ads, or embeds that don't have reserved space.
Every image, video, and iframe needs explicit width and height attributes. The browser uses those dimensions to allocate space before the resource loads. If you don't provide them, the element has zero height until it finishes downloading, and everything below it jumps when it appears.
For responsive images that scale with the viewport, use aspect-ratio in CSS:
img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
The browser calculates the height based on the width and the ratio, so no layout shift occurs.
Dynamic content injected after page load is the second most common cause. In support tickets I've handled, the usual offender was cookie banners or notification bars that push content down when they appear. Reserve space for them in your layout, or slide them in from the edge instead of pushing from the top.
For WordPress, check your header.php for any dynamically inserted elements. If you have a notice bar controlled by a plugin, make sure it has a min-height set even when hidden:
.site-notice {
min-height: 50px;
transition: opacity 0.3s;
}
Web fonts are another subtle cause. When a custom font loads, text reflows if the fallback font has different metrics. Use font-display: swap and size-adjust to minimize the shift:
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
size-adjust: 95%;
}
The size-adjust property scales the fallback font to match the custom font's dimensions more closely, reducing reflow when the swap happens.
What to check first: your hosting stack
Before you optimize images or defer scripts, make sure your hosting environment isn't the bottleneck. I've seen developers spend hours tweaking front-end code while their server was responding in 3 seconds because of a misconfigured PHP handler or a full disk.
Run these checks:
- TTFB under load: Use
ab -n 100 -c 10 https://yoursite.com/to send 100 requests with 10 concurrent connections. If response time spikes, your server can't handle even modest traffic. - Opcache enabled: Run
php -i | grep opcache.enableon the command line. If it's off, every page request recompiles your PHP files. - Database queries: Enable the Query Monitor plugin in WordPress or check your app's logs for slow queries. Anything over 50ms is worth optimizing.
- Disk I/O: Run
iostat -x 1and watch the %util column. If it's above 80%, your disk is saturated and every file read is slow.
On shared hosting, you have limited control over these. If TTFB is consistently slow and you've already optimized your code, it's time to move to a VPS or managed hosting with better resource allocation.
Measuring the change
Run PageSpeed Insights before and after each fix. Use the lab data for quick feedback and the field data (from Chrome User Experience Report) to see real-world impact over time. Lab scores can improve immediately; field data takes about a month to reflect changes.
If you don't see movement after a fix, check the PageSpeed diagnostics section. It will tell you which specific elements are still causing issues—usually images you missed or third-party scripts you can't control.
For scripts you can't defer (like payment gateways or critical analytics), consider using a facade: load a lightweight placeholder and only fetch the real script when the user interacts with it. The YouTube embed facade pattern works well here—show a static thumbnail and load the iframe only on click.
How long until field data updates?
Chrome User Experience Report aggregates data over a 28-day rolling window. You'll see real-world Core Web Vitals reflect your changes after most visitors have seen the updated site, usually three to four weeks.
Can I pass Core Web Vitals on shared hosting?
Yes, if your TTFB is under 600ms and you optimize images and scripts. Shared hosting limits how much you can improve server response time, but front-end optimization still makes a big difference for LCP and CLS.
Does a CDN improve INP?
Not directly. INP measures browser responsiveness to interactions, which happens client-side. A CDN improves LCP by serving images and static assets faster, but INP depends on reducing JavaScript execution time.
Why does my score vary between tests?
Lab tests run under synthetic conditions and can vary based on server load, network latency, and caching state. Run three tests and average them. Field data is more stable because it reflects real user experiences over time.
What to measure after you deploy
Once you've applied these fixes, monitor your field data in Google Search Console under Core Web Vitals. The report shows which URLs pass or fail for mobile and desktop, broken down by issue type.
Prioritize fixing pages with the most traffic first—your homepage, top landing pages from search, and product or service pages. A perfect score on a rarely visited page doesn't move the needle.
Set up uptime monitoring and performance checks with a tool like UptimeRobot or Pingdom so you catch regressions before they hurt your rankings. A plugin update or theme change can reintroduce slow scripts or missing image dimensions, and you want to know immediately.
Core Web Vitals optimization is not a one-time project. It's a checklist you revisit every time you add a feature, change themes, or integrate a new tool.
![Core Web Vitals Optimization: [Solved] 4 Fixes That Work](/images/blog/core-web-vitals-optimization-solved-4-fixes-that-work-2.jpg)