← ジャーナルに戻る
· 9 分で読了

Core Web Vitals Explained: How to Improve Page Speed Without Paid Tools

Page speed is a confirmed Google ranking factor. Here's how to measure and improve Core Web Vitals (LCP, INP, CLS) using only free tools — no Ahrefs, no paid audits.

Google has been clear: page speed affects rankings. Not as heavily as content relevance, but enough that a slow page can hold back otherwise great content. The good news is that every tool you need to measure and fix page speed is free. You don’t need Ahrefs, you don’t need a paid site auditor, and you definitely don’t need a $500/month enterprise platform to tell you your images are too big.

This guide covers what Core Web Vitals are, how to measure them for free, and the specific optimizations that move the needle.

What Core Web Vitals Actually Measure

Google evaluates page experience through three metrics. As of 2024, the set is:

LCP (Largest Contentful Paint) — How long it takes for the largest visible element to render. Usually an image, a hero section, or a large text block. Your LCP element is the thing the user sees first and biggest. If it takes more than 2.5 seconds to appear, Google considers your page “needs improvement.” Over 4 seconds is “poor.”

INP (Interaction to Next Paint) — Replaced FID in March 2024. Measures how quickly your page responds when a user clicks, taps, or types. It captures the full interaction latency — from input to next visual change. Under 200ms is good. Over 500ms is poor. INP is harder to optimize than FID because it measures the worst-case interaction, not just the first one.

CLS (Cumulative Layout Shift) — How much your page layout shifts around as it loads. You’ve experienced this: you try to click a link, but an image loads above it and pushes it down. Under 0.1 is good. Over 0.25 is poor.

MetricGoodNeeds ImprovementPoor
LCP< 2.5s2.5s - 4.0s> 4.0s
INP< 200ms200ms - 500ms> 500ms
CLS< 0.10.1 - 0.25> 0.25

All three are measured on real user data through the Chrome User Experience Report (CrUX), not synthetic lab tests. This distinction matters — you’ll see different numbers depending on whether you’re looking at field data (real users) or lab data (simulated).

Free Tools to Measure Core Web Vitals

PageSpeed Insights (PSI)

The fastest way to check any page. Go to pagespeed.web.dev, paste your URL, and get both lab data (Lighthouse simulation) and field data (real Chrome users) side by side.

Lab data tells you what’s technically wrong. Field data tells you what real users actually experience. If your lab LCP is 1.8s but your field LCP is 3.5s, real users on slower devices and connections are having a worse experience than your test environment shows.

Always prioritize field data over lab data. That’s what Google uses for rankings.

Google Search Console

GSC has a dedicated Core Web Vitals report. It shows which URL groups are passing, which need improvement, and which are poor — based on real user data aggregated across your site.

This is the most actionable report because it groups URLs by template. If your /blog/ pages all have poor LCP, fixing the blog layout template fixes every blog page at once.

For a complete walkthrough of what else GSC can do for your SEO, see our Google Search Console guide.

Chrome DevTools (web-vitals library)

For the most precise measurement during local development, install Google’s web-vitals library:

npm install web-vitals

Then add this to your app:

import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);

Open Chrome DevTools, navigate to your page, and watch the console. You’ll get exact metric values with attribution — which element caused the LCP, which interaction was slowest for INP, which layout shifts contributed to CLS.

Lighthouse CLI

If you want to audit multiple pages in CI or batch:

npm install -g lighthouse
lighthouse https://example.com --output=json --output-path=./report.json

Lighthouse gives you the lab data plus a full audit checklist — every opportunity, diagnostic, and passed audit with specific recommendations.

Fixing LCP (Largest Contentful Paint)

LCP is almost always caused by one of three things: a large image, a slow server response, or render-blocking resources.

Optimize Images

Images are the #1 cause of poor LCP. A 3MB hero image served as a full-resolution PNG will tank your LCP regardless of how fast your server is.

Use modern formats. WebP and AVIF are 25-50% smaller than JPEG/PNG with no visible quality loss. Every modern browser supports WebP. AVIF is supported in Chrome, Firefox, and Safari 16+.

Set explicit dimensions. If your LCP image doesn’t have width and height attributes (or aspect-ratio CSS), the browser can’t reserve space for it. It downloads the image, recalculates layout, and shifts everything — tanking both LCP and CLS.

<!-- Bad: no dimensions -->
<img src="hero.jpg" alt="Hero">

<!-- Good: explicit dimensions -->
<img src="hero.webp" width="1200" height="630" alt="Hero" fetchpriority="high">

Use fetchpriority="high" on your LCP image. This tells the browser to prioritize downloading it above other resources. It’s supported in all modern browsers.

Lazy-load below-the-fold images. Don’t make the browser download images the user can’t see yet:

<img src="below-fold.webp" loading="lazy" width="800" height="600" alt="...">

Serve responsive images. Don’t send a 2400px image to a phone. Use srcset:

<img
  src="hero-800.webp"
  srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
  sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
  width="1200" height="630"
  alt="Hero"
  fetchpriority="high"
>

Reduce Server Response Time (TTFB)

Your Time to First Byte affects everything downstream. If your server takes 800ms to respond, your LCP floor is already 800ms before any rendering happens.

Use a CDN. Cloudflare, BunnyCDN, or any edge network dramatically reduces TTFB by serving content from a location close to the user. If you’re on Cloudflare Pages, Vercel, or Netlify, you already have a CDN.

Cache aggressively. Set appropriate Cache-Control headers. Static assets should be cached for a year:

Cache-Control: public, max-age=31536000, immutable

HTML pages should use shorter cache durations or revalidation:

Cache-Control: public, max-age=0, must-revalidate

Use SSR or SSG, not CSR. Client-side rendering means the browser downloads JavaScript, parses it, executes it, then renders content. Server-side rendering or static site generation sends pre-rendered HTML immediately. For content sites, there’s no reason to use CSR. Frameworks like Astro default to zero-JS static HTML.

Eliminate Render-Blocking Resources

CSS and JavaScript in the <head> block rendering. The browser can’t paint anything until it downloads and parses these files.

Inline critical CSS. Extract the CSS needed for above-the-fold content and inline it directly in a <style> tag. Defer the rest. Tools like critters (for Webpack) or Astro’s built-in CSS handling do this automatically.

Defer non-critical JavaScript. Any <script> that isn’t needed for initial render should have defer or async:

<script src="analytics.js" defer></script>
<script src="chat-widget.js" async></script>

defer downloads the script in parallel but executes it after HTML parsing. async downloads in parallel and executes as soon as it’s ready. For most analytics and tracking scripts, use defer.

Fixing INP (Interaction to Next Paint)

INP measures responsiveness to user input. Slow INP usually means JavaScript is blocking the main thread when the user tries to interact.

Break Up Long Tasks

Any JavaScript task that runs longer than 50ms is a “long task.” During that time, the page is unresponsive. A click during a long task gets queued and feels sluggish.

// Bad: blocking the main thread
function processItems(items) {
  items.forEach(item => {
    heavyComputation(item);
  });
}

// Better: yield to the main thread
async function processItems(items) {
  for (const item of items) {
    heavyComputation(item);
    await scheduler.yield(); // Let the browser handle input
  }
}

scheduler.yield() is available in Chrome 129+. For broader support, use setTimeout:

function yieldToMain() {
  return new Promise(resolve => setTimeout(resolve, 0));
}

Reduce JavaScript Bundle Size

Every kilobyte of JavaScript must be downloaded, parsed, and executed. A 500KB JavaScript bundle can add 300-500ms of main thread blocking on a mid-range phone.

Code-split. Load JavaScript only for the page that needs it. Route-based code splitting means your blog page doesn’t load the JavaScript for your pricing page.

Audit third-party scripts. Analytics, chat widgets, heatmaps, ad pixels — each one adds JavaScript. Run Lighthouse and look at the “Reduce JavaScript execution time” audit. You’ll see a breakdown of every script and its impact.

Prefer CSS over JavaScript. Animations, dropdowns, and accordions that can be done in CSS don’t need JavaScript. CSS runs on the compositor thread and never blocks the main thread.

Use requestIdleCallback for Non-Urgent Work

// Defer analytics and telemetry
requestIdleCallback(() => {
  sendAnalytics();
});

This tells the browser “do this when you have nothing more important to handle.” It keeps user-facing interactions responsive.

Fixing CLS (Cumulative Layout Shift)

CLS is the easiest Core Web Vital to fix. Layout shifts happen when elements move after the page has started rendering.

Always Set Dimensions on Images and Embeds

<!-- This causes layout shift -->
<img src="photo.jpg" alt="Photo">

<!-- This doesn't -->
<img src="photo.webp" width="800" height="600" alt="Photo">

Without dimensions, the browser doesn’t know how much space to reserve. It renders text, then the image loads and pushes everything down.

Reserve Space for Ads and Embeds

If you have ad slots, YouTube embeds, or third-party widgets, use CSS min-height or aspect-ratio to reserve space before the content loads:

.ad-slot {
  min-height: 250px;
}

.video-embed {
  aspect-ratio: 16 / 9;
}

Don’t Inject Content Above Existing Content

Banners, cookie notices, and “sign up for our newsletter” pop-ups that push content down cause layout shifts. If you must use them:

  • Use position: fixed or position: sticky so they overlay content instead of pushing it
  • Reserve space with CSS before the dynamic content loads
  • Use transform animations instead of changing height or top

Avoid Web Font Layout Shifts

Custom fonts that load after the initial render cause text to reflow — system font renders first, then the custom font swaps in with different metrics.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;
}

font-display: swap tells the browser to render text immediately with a fallback font, then swap to the custom font when it loads. To minimize the shift, choose a fallback font with similar metrics (size, x-height, letter-spacing).

For even better results, use size-adjust in your @font-face to match the fallback font’s metrics:

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 100.5%;
  ascent-override: 92%;
  descent-override: 22%;
}

Mobile Performance Matters Most

Google uses mobile-first indexing and mobile CrUX data for ranking. Your desktop scores are almost irrelevant. A page that loads in 1.2s on your MacBook might take 4.5s on a mid-range Android phone over a 4G connection.

Always test with mobile throttling. In Chrome DevTools, open the Performance tab, set network to “Slow 4G” and CPU to “4x slowdown.” This simulates a realistic mobile experience.

Check PSI’s mobile tab, not desktop. If your mobile scores are in the green, you’re in good shape. If they’re not, your desktop scores don’t matter.

How Core Web Vitals Connect to Other SEO Efforts

Core Web Vitals don’t exist in isolation. They’re one part of a technical SEO foundation:

  • Fast pages get crawled more efficiently — Googlebot has a crawl budget, and slow pages eat into it
  • Good page experience compounds with great content — Google rewards pages that are both relevant and fast
  • Schema markup and Core Web Vitals together create a technically optimized page that both search engines and users love
  • Run a technical SEO audit to catch issues beyond speed — broken links, missing canonicals, and crawl errors

Monitoring Core Web Vitals Over Time

Fixing Core Web Vitals isn’t a one-time task. As you add content, install new tools, and change layouts, your scores will fluctuate.

Set up GSC alerts. The Core Web Vitals report in Google Search Console updates weekly. Check it regularly for new URL groups that regress.

Track in your CI pipeline. Run Lighthouse on every deployment and fail the build if scores drop below a threshold:

lighthouse https://staging.example.com \
  --only-categories=performance \
  --budget-path=./lighthouse-budget.json \
  --output=json --output-path=./lh-report.json

Use the web-vitals library in production. Send real user metrics to your analytics:

import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics({ name, value, rating }) {
  navigator.sendBeacon('/api/vitals', JSON.stringify({ name, value, rating }));
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

This gives you field data for every page, not just what CrUX reports.

Quick Wins Checklist

Most Core Web Vitals improvements come from a handful of changes:

  • Convert all images to WebP or AVIF
  • Add width and height to every <img> tag
  • Add fetchpriority="high" to your LCP image
  • Add loading="lazy" to below-the-fold images
  • Defer all non-critical <script> tags
  • Set Cache-Control headers on static assets
  • Use font-display: swap on custom fonts
  • Reserve space for ads and embeds with min-height or aspect-ratio
  • Remove unused JavaScript (run Lighthouse “Reduce JavaScript execution time” audit)
  • Test mobile performance with PSI

Start with images — they’re the single biggest LCP improvement for most sites. Then tackle JavaScript. CLS fixes are quick and high-confidence. The entire process takes 2-4 hours for a typical content site, and every improvement compounds across every page that shares the same template.

Want to run this analysis on your own site?

ZensInk Pro automates this pipeline. One command, from seed keywords to content plan.

Get Pro →