Overview
TTFB, FCP, image weight, fonts, and script cost sit underneath the three Core Web Vitals and decide whether a page hits its LCP and INP targets. These fixes are cheap at architecture time and expensive on a legacy build. For the LCP, INP, and CLS targets themselves, see core-web-vitals; for the fix loop, see optimize-core-web-vitals.
Keep TTFB at or under 0.8 seconds
web.dev treats 0.8 seconds at the 75th percentile as good and over 1.8 seconds as poor. TTFB is not a Core Web Vital, but every other metric starts after it.
- Serve static pages from a CDN edge (GitHub Pages, Vercel, Netlify, Cloudflare Pages).
- Cache dynamic HTML at the edge with
s-maxageandstale-while-revalidate; see cloudflare-cache-rules. - Remove database round-trips from the request path by caching reads, batching queries, or moving the page to static. This is the most common TTFB cost.
Get FCP to 1.8 seconds or less
web.dev rates FCP good at 1.8 seconds or less and poor above 3.0 seconds. Render the first paint on the server, because an empty <div id="root"> makes FCP equal to JavaScript parse time. Preconnect to a third-party font or CDN origin: <link rel="preconnect" href="https://fonts.example.com" crossorigin>.
Serve modern image formats and lazy-load below the fold
- Use
<picture>with AVIF first, WebP second, and a JPEG or PNG fallback. Savings vary by image, so measure your own. - Add
loading="lazy"to every image and iframe below the fold. - Give the LCP image
fetchpriority="high"and neverloading="lazy". - Set
widthandheighton every<img>to reserve space.
See image-seo for filenames, alt text, and sitemaps.
Load fonts with font-display: swap and self-host them
Web fonts hold text back until they load or a timeout fires.
@font-face {
font-family: "Inter";
src: url("/fonts/inter-var.woff2") format("woff2");
font-display: swap;
font-weight: 100 900;
}- Self-host to avoid DNS, TCP, and TLS round-trips to a third party; otherwise preconnect to its origin.
- Subset the font to the glyphs the site uses.
- Tune the fallback
@font-facewithsize-adjust,ascent-override, anddescent-overrideso the swap does not shift layout.
Inline critical CSS and defer the rest
The browser will not paint until it has the page’s CSS.
- Extract above-the-fold CSS with Critical or Critters and inline it in
<style>in<head>. About 14 KB of compressed HTML fits in the first TCP round-trip under the common initial congestion window, a practitioner target. - Load the rest with
<link rel="preload" as="style" onload="this.rel='stylesheet'">or amedia="print"swap, plus a<noscript>fallback. - Audit unused CSS with Coverage in Chrome DevTools.
Defer JavaScript
- Ship first-party scripts as
type="module", which defers by default, or adddefer. - Load analytics, chat, A/B testing, and consent banners on
requestIdleCallbackor after the first interaction. - Audit third-party scripts on a schedule, since vendor code accumulates and much of it never runs on a given page.
- Import individual functions (
lodash-esnamed imports) instead of whole libraries.
Measure field data and synthetic budgets
- Fail the build in Lighthouse CI when a metric crosses a budget set in
lighthouserc.json. Start from the current measured weight and tighten; the numbers are a team choice, not a Google threshold. - Run WebPageTest with throttled 4G and a mid-tier phone profile; lab runs on a fast laptop do not reflect users.
- Confirm real-world impact with the
web-vitalslibrary and CrUX. See core-web-vitals.