Improving Core Web Vitals in Practice — How I Cut LCP from 3s to 1.2s
LCP (Largest Contentful Paint) directly impacts Google search rankings. By sequentially applying image optimization, font loading strategy, and code splitting, LCP was reduced from 3.0s to 1.2s, and CLS dropped from 0.28 to 0.04.
Key Summary
- LCP (Largest Contentful Paint) is a core metric that directly impacts Google search rankings.
- By sequentially applying image optimization, font loading strategy, and code splitting, LCP was reduced from 3.0 seconds to 1.2 seconds.
- CLS was improved from 0.28 to 0.04 by specifying explicit dimensions for images and ad areas and eliminating layout shifts.
- Based on a real production Next.js project — the same principles apply to React and Vue projects.
Introduction — Why Core Web Vitals?
When Google rolled out the Page Experience update in 2021, Core Web Vitals moved beyond simple UX metrics and became an SEO ranking factor. Since then, Google has continued increasing the weight of these signals, and as of 2024, INP (Interaction to Next Paint) has fully replaced FID as an evaluation metric.
An e-commerce project I operate was scoring a dismal 38 points on PageSpeed Insights mobile just six months ago. LCP was 3.0 seconds, CLS was 0.28, INP was 320ms — all three metrics in the "Needs Improvement" zone. This article documents the entire process of turning those numbers around.
How Can You Quickly Understand the Three Core Web Vitals?
LCP — The "First Impression" Speed of a Page
LCP measures the time it takes for the largest content element in the viewport (usually the hero image or H1 text) to render. Google's threshold is 2.5 seconds or less for Good; above 4.0 seconds is Poor.
On e-commerce sites, the hero banner image is overwhelmingly the LCP candidate. If this one image loads too slowly, every other optimization becomes meaningless.
CLS — Does the Layout "Jump"?
CLS (Cumulative Layout Shift) measures how much elements unexpectedly shift during page loading. Late-loading ad banners or images without explicit dimensions cause CLS to spike. 0.1 or below is the Good threshold.
INP — How Quickly Does the Page Respond to User Input?
INP measures the response delay for all interactions — clicks, taps, keyboard input. It replaced FID in March 2024, and 200ms or below is the Good threshold. Heavy JavaScript bundles block the main thread and worsen INP.
Diagnosing the Problem — Initial Measurements
Accurately recording metrics before optimization is the starting point. These tools were used in order:
- 1PageSpeed Insights — Provides both field data (real user data) and lab data (Lighthouse) simultaneously
- 2Chrome DevTools > Performance tab — Inspect rendering timeline and LCP candidate element
- 3WebPageTest — Real-world measurement from CDN edge servers, visual confirmation via filmstrip
- 4Vercel Analytics / Sentry — Core Web Vitals collected from real user sessions
The diagnosis narrowed the bottlenecks to three areas:
- Hero image — 4.2MB JPEG, inserted directly via
tag with no optimization - Google Fonts — 3 font families loaded synchronously via
@import - Bundle size — 1.8MB main chunk, multiple libraries included without tree-shaking
What Were the Three Methods That Cut LCP from 3.0s to 1.2s?
Method 1 — Image Optimization and Preload
This had the biggest impact. The hero image handling was completely redesigned.
Before
<img src="/banner.jpg" alt="Main Banner" />After — Next.js Image Component + WebP Conversion
import Image from 'next/image';
<Image
src="/banner.webp"
alt="Main Banner"
width={1920}
height={800}
priority // LCP target images must always have priority
quality={80}
sizes="(max-width: 768px) 100vw, 1920px"
/>Adding the priority prop alone causes Next.js to automatically inject a tag for the image. After converting to WebP, file size dropped from 4.2MB to 340KB — approximately a 92% reduction.
For pure HTML/React projects, add a preload tag directly in :
<link
rel="preload"
as="image"
href="/banner.webp"
type="image/webp"
/>This single change dropped LCP from 3.0s to 1.8s.
Method 2 — Font Loading Strategy
Loading Google Fonts via @import causes the browser to pause CSS parsing and make a font request. This wait time substantially increases LCP.
Before
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
</style>After — + display=swap + Self-hosting
<!-- Step 1: Preconnect to eliminate DNS resolution delay -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Step 2: Load asynchronously with display=swap -->
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
rel="stylesheet"
media="print"
onload="this.media='all'"
/>Using media="print" then switching to media="all" on load causes the font CSS to load asynchronously without blocking rendering. display=swap displays a fallback font immediately while the web font loads, keeping text visible.
For maximum performance, self-hosting fonts eliminates the Google Fonts round-trip entirely:
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/inter-regular.woff2') format('woff2');
}This step brought LCP down from 1.8s to 1.4s.
Method 3 — Code Splitting and Bundle Optimization
A 1.8MB main chunk means users must download and parse nearly 2MB of JavaScript before the page becomes interactive.
Key actions taken:
- 1Dynamic imports — Heavy libraries loaded only when needed:
// Before: always loaded
import Chart from 'chart.js';
// After: loaded only when the chart component is actually rendered
const Chart = dynamic(() => import('chart.js'), { ssr: false });- 1Tree-shaking audit — Replaced
import _ from 'lodash'with individual function imports:
// Before: loads entire lodash library
import _ from 'lodash';
// After: loads only debounce
import debounce from 'lodash/debounce';- 1Third-party script lazy loading — Analytics and chat widget scripts deferred to load after page is interactive:
<script src="/analytics.js" defer></script>After bundle optimization, main chunk size dropped from 1.8MB to 620KB, and LCP improved from 1.4s to 1.2s.
How Was CLS Reduced from 0.28 to 0.04?
The two biggest CLS offenders were ads and images without explicit dimensions.
Ad Area: Reserve Space in Advance
/* Reserve minimum height so ad slot never causes layout shift */
.ad-container {
min-height: 90px; /* banner ad */
min-width: 728px;
}Images: Always Specify Width/Height
/* Before: no dimensions → causes layout shift during load */
<img src="/product.jpg" alt="Product" />
/* After: explicit dimensions → browser reserves space */
<Image
src="/product.jpg"
alt="Product"
width={400}
height={300}
/>Dynamic Content: Use Placeholders
// Show skeleton placeholder while data is loading
{isLoading ? (
<div className="h-48 bg-gray-200 animate-pulse rounded" />
) : (
<ProductCard data={data} />
)}These three changes brought CLS from 0.28 down to 0.04.
Results Summary
| Metric | Before | After | Change |
|---|---|---|---|
| LCP | 3.0s | 1.2s | -60% |
| CLS | 0.28 | 0.04 | -86% |
| INP | 320ms | 95ms | -70% |
| PageSpeed Mobile | 38 | 91 | +53 pts |
Check Your Own Site's Core Web Vitals
Use these tools to measure your current score:
- PageSpeed Checker — Check PageSpeed Insights score instantly
- Meta Tag Checker — Verify SEO and OG tag configuration simultaneously
Frequently Asked Questions (FAQ)
Q1. Does improving Core Web Vitals directly raise search rankings?
Core Web Vitals are one of Google's official ranking factors. Google has publicly confirmed they are a Page Experience signal. However, other factors like content quality and backlinks still carry more weight, so Core Web Vitals improvement should be viewed as a necessary condition rather than a guarantee.
Q2. Which Core Web Vitals metric has the biggest impact on rankings?
LCP is the most visible to users and has the most direct impact on bounce rates, which indirectly affects rankings. CLS also directly affects UX, so both are high-priority targets. INP's influence as a newer metric is still being established.
Q3. How do I check my LCP element?
Open Chrome DevTools, go to the Performance tab, record a page load, and look for "LCP" in the timeline. Alternatively, running PageSpeed Insights will tell you exactly which element is the LCP candidate.
Q4. Does Google Fonts hurt Core Web Vitals?
Yes. Loading Google Fonts via @import blocks rendering and is a common cause of LCP degradation. Using combined with display=swap or self-hosting fonts is strongly recommended.
Q5. How much does Next.js help with Core Web Vitals?
Quite significantly. Next.js provides automatic image optimization (WebP conversion, lazy loading, size optimization), built-in font optimization (next/font), and route-based code splitting as built-in features. These alone can lead to dramatic Core Web Vitals improvements without extra configuration.
Q6. How often should Core Web Vitals be checked?
Monitoring at least once a month is recommended. PageSpeed Insights reflects real user data collected over the past 28 days, so changes take time to appear. Using Google Search Console to track field data trends over time is more practical than one-off measurements.
🔧 Related Free Tools
Related Products (Core Web Vitals)[Ad/Affiliate]
As an Amazon Associate, Coupang Partner, and AliExpress affiliate, I earn from qualifying purchases at no extra cost to you.
Related Posts
2026년 가장 인기 있는 AI 코딩 도구 Claude Code, Cursor, GitHub Copilot 3종을 월 가격·1M 컨텍스트·한국어...
IT블로그 SEO 2026 — 구글 알고리즘 변화와 대응 전략2026년 블로그 SEO 완벽 가이드. 구글 E-E-A-T·AI Overview·코어 업데이트 대응 전략. 롱테일 키워드·FAQ 구조·테크니컬 ...
IT2026 NordVPN vs ExpressVPN vs Surfshark — VPN 속도·가격·보안 비교2026년 기준 NordVPN, ExpressVPN, Surfshark 3대 VPN의 속도, 가격, 서버 수, 노로그 정책, 스트리밍 지원을 비...
IT2026 맥북 에어 M4 vs 삼성 갤럭시북4 vs 레노버 요가 — 개발자 노트북 비교2026년 기준 맥북 에어 M4, 삼성 갤럭시북4 프로, 레노버 요가 슬림 7i의 CPU, 배터리, 디스플레이, 개발 워크플로우를 비교합니다....