
Core Web Vitals
Tune Largest Contentful Paint and related load paths on marketing sites and app shells before you ship or re-launch.
Overview
Core Web Vitals is an agent skill for the Ship phase that guides Largest Contentful Paint optimization across TTFB, resource loading, and render timing.
Install
npx skills add https://github.com/addyosmani/web-quality-skills --skill core-web-vitalsWhat is this skill?
- Explains what counts as the LCP element (img, video poster, background image, text blocks)
- Maps LCP to TTFB, resource download, and paint with a clear timeline
- TTFB target under 800ms with edge runtime and stale-while-revalidate Cache-Control examples
- Hero image preload with imagesrcset, fetchpriority=high, and picture/avif/webp fallbacks
- Serverless cold-start and CDN caching guidance for dynamic vs static paths
- TTFB optimization target under 800ms documented in the LCP reference
Adoption & trust: 8.3k installs on skills.sh; 2.2k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your hero content paints late because TTFB, unoptimized images, or missing preload priorities blow past Google's LCP thresholds.
Who is it for?
Indie builders polishing a marketing site, SaaS shell, or storefront where Search Console or Lighthouse already reports poor LCP.
Skip if: Greenfield UI layout with no deployed URL yet, or teams that only need backend/API testing with no web vitals surface.
When should I use this skill?
Lighthouse, PageSpeed, or Search Console reports poor LCP or you are optimizing the largest above-the-fold element before ship.
What do I get? / Deliverables
You get prioritized HTML, caching, CDN, and image patterns aimed at a faster LCP on the largest above-the-fold element.
- LCP-focused change list (preload, formats, caching)
- Updated hero or critical asset markup patterns
Recommended Skills
Journey fit
Core Web Vitals are a launch-quality gate: you fix LCP, TTFB, and hero assets when the product is nearly ready to ship, not during raw ideation. Perf subphase is where solo builders measure real-user metrics and apply preload, CDN, and image-format fixes tied to LCP.
How it compares
Use as a vitals-focused playbook instead of generic “make it faster” chat advice without LCP element targeting.
Common Questions / FAQ
Who is core-web-vitals for?
Solo and indie builders shipping web frontends who need agent-guided LCP fixes tied to real metrics, especially on content and SaaS sites.
When should I use core-web-vitals?
During Ship perf work before launch, after a hero or CDN change, or when validating a redesign against Core Web Vitals in production-like builds.
Is core-web-vitals safe to install?
It is reference documentation for code and headers; review the Security Audits panel on this Prism page and inspect the skill files in your repo before enabling auto-apply edits.
SKILL.md
READMESKILL.md - Core Web Vitals
# LCP optimization reference ## What is LCP? Largest Contentful Paint (LCP) measures when the largest content element in the viewport becomes visible. This is typically: - An `<img>` element - An `<image>` element inside `<svg>` - A `<video>` element with poster image - An element with a background image via `url()` - A block-level element containing text nodes ## LCP timeline ``` [ Server Response ][ Resource Load ][ Render ] TTFB Download Paint └─────────────────────────────────────┘ LCP Time ``` ## Detailed optimizations ### 1. Server response time (TTFB) Target: < 800ms **Causes:** - Slow server/database queries - No CDN/edge caching - Inefficient backend code - Cold starts (serverless) **Solutions:** ```javascript // Use edge functions for dynamic content // Vercel example export const config = { runtime: 'edge' }; // Use stale-while-revalidate caching // Cache-Control header res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate=300'); ``` ### 2. Resource load time **For images:** ```html <!-- Preload LCP image --> <link rel="preload" as="image" href="/hero.webp" imagesrcset="/hero-400.webp 400w, /hero-800.webp 800w" imagesizes="100vw" fetchpriority="high"> <!-- Modern format with fallback --> <picture> <source srcset="/hero.avif" type="image/avif"> <source srcset="/hero.webp" type="image/webp"> <img src="/hero.jpg" width="1200" height="600" fetchpriority="high" alt="Hero"> </picture> ``` **For text (web fonts):** ```css @font-face { font-family: 'Heading'; src: url('/fonts/heading.woff2') format('woff2'); font-display: swap; /* Show fallback immediately */ } ``` ### 3. Render blocking resources **Critical CSS pattern:** ```html <head> <!-- Inline critical CSS --> <style> /* Only above-fold styles, < 14KB */ .hero { /* ... */ } .nav { /* ... */ } </style> <!-- Defer non-critical CSS --> <link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> </head> ``` **Defer JavaScript:** ```html <!-- ❌ Blocks parsing --> <script src="/app.js"></script> <!-- ✅ Deferred (runs after HTML parsed) --> <script defer src="/app.js"></script> <!-- ✅ Module (deferred by default) --> <script type="module" src="/app.mjs"></script> ``` ### 4. Client-side rendering **Problem:** Content not in initial HTML. **Solutions:** **Server-side rendering (SSR):** ```javascript // Next.js export async function getServerSideProps() { const data = await fetchHeroContent(); return { props: { hero: data } }; } ``` **Static site generation (SSG):** ```javascript // Next.js export async function getStaticProps() { const data = await fetchHeroContent(); return { props: { hero: data }, revalidate: 3600 }; } ``` **Streaming SSR:** ```jsx // React 18+ import { Suspense } from 'react'; function Page() { return ( <Suspense fallback={<HeroSkeleton />}> <Hero /> </Suspense> ); } ``` ## Framework-specific tips ### Next.js ```jsx import Image from 'next/image'; // LCP image with priority <Image src="/hero.jpg" priority fill sizes="100vw" alt="Hero" /> ``` ### Nuxt ```vue <NuxtImg src="/hero.jpg" preload loading="eager" sizes="100vw" /> ``` ### Astro ```astro --- import { Image } from 'astro:assets'; import hero from '../assets/hero.jpg'; --- <Image src={hero} loading="eager" decoding="sync" alt="Hero" /> ``` ## Debugging LCP ```javascript // Identify LCP element new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); const lastEntry = entries[entries.length - 1]; console.log('LCP:', { element: lastEntry.element, time: lastEntry.startTime, size: lastEntry.size, url: lastEntry.url, renderTime: lastEntry.renderTime, loadTime: lastEntry.loadTime }); }).observe({ type: 'largest-contentful-paint', buffered: true }); ``` ## Common issues |