Blog

SEO 8 min read

Core Web Vitals for PHP sites: how to fix LCP, INP and CLS

Core Web Vitals are measured in the browser, but on a PHP site a large part of the result is decided on the server. This guide shows where to look first, which PHP, nginx and front-end changes move the numbers, and how to measure without fooling yourself.

Cover illustration: Core Web Vitals for PHP sites: how to fix LCP, INP and CLS

TL;DR

  • The three metrics: Largest Contentful Paint (LCP) under 2.5 s, Interaction to Next Paint (INP) under 200 ms, Cumulative Layout Shift (CLS) under 0.1, judged at the 75th percentile of real users.
  • Start with server response time (TTFB): OPcache, a tuned PHP-FPM pool, page or fragment caching and a CDN usually give the biggest LCP win on PHP sites.
  • Fix the LCP element deliberately: size the hero image, serve WebP, set fetchpriority="high" and never lazy-load it.
  • Reserve space for images, ads and embeds to stop layout shifts; use font-display: swap and preloaded fonts.
  • Measure with field data (Search Console, CrUX) first, then reproduce with Lighthouse and the browser's performance panel.

Core Web Vitals for PHP sites are not a front-end-only topic. A slow PHP page can spend a second just producing HTML, and no amount of image optimisation recovers that. At the same time, a very fast server does not help if a 1.5 MB hero image blocks the largest paint. Here is how we approach both sides on our own PHP sites (integrono.com runs PHP-FPM behind nginx and Cloudflare) and on client projects.

What Core Web Vitals measure

  • LCP (Largest Contentful Paint): when the biggest visible element (usually a hero image or a heading block) is rendered. Good is 2.5 seconds or less.
  • INP (Interaction to Next Paint): how quickly the page responds visually after clicks, taps and key presses, across the whole visit. Good is 200 ms or less.
  • CLS (Cumulative Layout Shift): how much content jumps around while loading. Good is 0.1 or less.

Google evaluates them on field data from real Chrome users, at the 75th percentile, separately for mobile and desktop. That means one slow template or one slow country can fail the whole group. Lab tools such as Lighthouse are for debugging, not for the final score.

Step 1. Measure the right way

  1. Open the Core Web Vitals report in Google Search Console to see which URL groups fail and on which metric.
  2. Check PageSpeed Insights for one URL from each failing group. The top "field data" block is what counts; the Lighthouse block below is a lab test that explains why.
  3. Use the Performance panel in Chrome DevTools with CPU 4x slowdown and "Slow 4G" to reproduce a typical mobile experience.
  4. For ongoing monitoring, collect real-user data with the web-vitals JavaScript library and send it to your analytics.
<script type="module">
  import {onLCP, onINP, onCLS} from 'https://unpkg.com/web-vitals@4?module';
  const send = (m) => navigator.sendBeacon('/vitals', JSON.stringify({
    name: m.name, value: m.value, id: m.id, url: location.pathname
  }));
  onLCP(send); onINP(send); onCLS(send);
</script>

Step 2. Fix server response time (TTFB) first

LCP includes the time before the first byte arrives. If your HTML takes 800 ms to generate, you have already spent a third of the budget. On PHP sites the usual levers are:

  • Enable OPcache and make sure it is large enough to hold all scripts. Without it PHP recompiles files on every request.
  • Tune PHP-FPM. Too few workers queue requests, too many exhaust memory. Watch the PHP-FPM status page for "listen queue" and "max children reached".
  • Cache whole pages for anonymous visitors (nginx fastcgi_cache, a Laravel response cache, or Cloudflare cache rules) and cache fragments or query results with Redis.
  • Fix slow queries. Turn on the slow query log, look for N+1 patterns and add indexes. A page that runs 120 queries is slow regardless of PHP version.
  • Keep PHP current. Newer PHP versions are faster and use less memory.
# php.ini (production)
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0   ; reload php-fpm on each deploy

# php-fpm pool
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500

Set pm.max_children from real memory: divide the memory you can give PHP by the average size of a worker process (check with ps). With validate_timestamps=0 remember to reload PHP-FPM after every deploy, otherwise old code stays in memory.

At the web server level enable HTTP/2 or HTTP/3, Brotli or gzip, and long-lived caching headers for static files:

location ~* \.(?:css|js|webp|avif|jpg|png|svg|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;

Use versioned file names or a ?v= query based on the file's modification time so that "immutable" never serves stale assets.

Step 3. Fix the LCP element

Find the LCP element in PageSpeed Insights or the DevTools Performance panel. On most content pages it is a hero image. Then:

  • Do not lazy-load it. loading="lazy" on the LCP image delays it. Use fetchpriority="high" instead.
  • Serve the right size. A 2400 px image for a 900 px slot wastes bytes. Use srcset and sizes.
  • Use WebP or AVIF. Typical savings are 30 to 60 percent against JPEG at similar quality.
  • Avoid chains. An image set via CSS background or injected by JavaScript is discovered late. Put it in the HTML as an <img>.
  • Preconnect or self-host third-party origins that the LCP depends on (fonts, image CDN).
<img src="/images/hero-1200.webp"
     srcset="/images/hero-640.webp 640w, /images/hero-1200.webp 1200w"
     sizes="(max-width: 1040px) 92vw, 960px"
     width="1200" height="630"
     alt="Descriptive alt text"
     decoding="async" fetchpriority="high">

This is the pattern the article covers on this blog use: two WebP sizes, explicit dimensions and high fetch priority for the hero.

Step 4. Remove layout shifts (CLS)

  • Always set width and height (or aspect-ratio in CSS) on images, videos and iframes so the browser reserves the space.
  • Reserve space for banners, cookie notices and ads. Insert them into a container with a fixed minimum height instead of pushing content down after load.
  • Fonts: use font-display: swap or optional, preload the main font file and subset it. Pick a fallback font with similar metrics.
  • Do not inject content above existing content unless the user triggered it.
  • Animate with transform and opacity, not with properties that change layout (top, height).

Step 5. Improve INP: less JavaScript on the main thread

INP is the newest metric and the one PHP developers neglect, because the server does not influence it directly. The causes are in the browser: long tasks from large bundles, heavy third-party scripts (analytics, chat widgets, tag managers) and handlers that do too much work synchronously.

  • Audit third-party scripts. Remove what nobody uses, load the rest with defer or after the first interaction.
  • Split long tasks. Break work into chunks with requestIdleCallback, setTimeout or the Scheduler API; move heavy computation to a Web Worker.
  • Keep the DOM small. Thousands of nodes make every update slow.
  • Avoid forced synchronous layouts in loops (reading and writing layout properties alternately).
  • Show immediate feedback (a pressed state or spinner) before starting slow work.

Step 6. Guard it in your deployment process

Performance regresses quietly: a new tag, a heavier hero, an extra query on the home page. Add Lighthouse CI or a scheduled PageSpeed API check for your five most important templates, set budgets (LCP, total JS size, image weight) and alert when they are exceeded. Compare TTFB before and after every release; if it jumps, the cause is almost always a query or a cache that no longer hits.

A realistic order of work

  1. Measure field data and identify failing URL groups.
  2. Enable OPcache, review PHP-FPM settings, add full-page caching for anonymous traffic.
  3. Fix hero images: format, size, priority, dimensions.
  4. Reserve space for everything that loads late.
  5. Reduce and defer JavaScript; remove unused third parties.
  6. Re-measure after 28 days, because field data is a rolling window.

Expect improvements to appear in Search Console gradually: the report uses roughly the last 28 days of data, so a fix shipped today is fully visible only after about a month.

How we can help

We do performance work on PHP and Laravel projects as part of a technical SEO engagement and as one-off tuning: caching, queries, images and front-end fixes shipped in code. If you want to know which of the changes above would pay off on your site, book a consultation or a server analysis. For the wider checklist around crawling and indexing, read our technical SEO audit checklist.

Frequently asked questions

What are good Core Web Vitals scores?

LCP of 2.5 seconds or less, INP of 200 milliseconds or less and CLS of 0.1 or less, all measured at the 75th percentile of real user visits.

Do Core Web Vitals affect Google rankings?

They are part of Google's page experience signals and a ranking factor of moderate weight. Relevance and content matter more, but a slow or unstable page also converts worse, which is often the bigger business cost.

How does PHP affect Core Web Vitals?

Mostly through server response time (TTFB), which feeds into LCP. OPcache, tuned PHP-FPM, caching, fast queries and a current PHP version reduce it. PHP does not directly cause INP or CLS issues, those come from front-end code and layout.

Why does PageSpeed Insights show different numbers than Search Console?

PageSpeed Insights shows a single-URL lab test plus field data when available, while Search Console reports field data grouped by URL type over roughly 28 days. Judge success by the field data.

How long until improvements show in Search Console?

The Core Web Vitals report uses a rolling 28-day window of real user data, so allow about four weeks after shipping a fix to see the full effect.

Free discovery call

Bring a messy stack or stalled integration - Integrono will map the smartest first move.

You get a senior take on budget traps, dependencies and whether to buy, glue or build - before paid work starts. The CEO stays in the loop.