LCP Astro 4+

Fix LCP in Astro

Largest Contentful Paint (LCP) is a critical Core Web Vital that measures how quickly the main content of your page becomes visible. In Astro applications, common issues include unoptimized images, render-blocking resources, and inefficient data loading patterns that delay the largest visible element.

This guide walks through five targeted fixes for LCP in Astro, with real code examples and before/after performance comparisons. Each step addresses a specific bottleneck in the Astro rendering pipeline.

Expected results

Following all five steps typically produces these improvements:

Before

3.1s

LCP score (Needs Improvement) -- unoptimized images, render-blocking scripts, no preloading in Astro project

After

0.9s

LCP score (Good) -- optimized with astro:assets, preloading, zero-JS islands, and edge deployment

Step-by-step fix

Use astro:assets for automatic image optimization

Astro's built-in <Image /> component from astro:assets automatically converts images to WebP/AVIF, generates responsive sizes, and adds width/height attributes to prevent layout shift. For the LCP image, combine it with eager loading and fetchpriority.

Common mistake: Do not use standard <img> tags for above-the-fold images. The Astro Image component handles format conversion, resizing, and optimization at build time, producing smaller files than any runtime solution.
Astro -- Before (Bad)
---
// src/pages/index.astro
---
Hero image
Astro -- After (Good)
---
import { Image } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---
Hero image

Preload critical resources in the head

Astro renders pages to static HTML by default, but the browser still needs hints about which resources to fetch first. Add <link rel="preload"> directives in your layout's <head> for fonts, hero images, and critical CSS. This tells the browser to start downloading these resources immediately rather than waiting to discover them during parsing.

Astro -- Layout head
---
// src/layouts/Base.astro
const { title, heroImage } = Astro.props;
---


  
  {title}
  
  {heroImage && (
    
  )}
  
  
  
  
  
  
  

Keep client-side JavaScript at zero for LCP content

Astro's islands architecture ships zero JavaScript by default. Only components with client:* directives send JS to the browser. For the LCP content area (hero section, above-the-fold text and images), never add client directives. Interactive elements below the fold should use client:visible to defer hydration until the user scrolls to them.

Astro -- Island architecture
---
import Hero from '../components/Hero.astro'; // Static -- zero JS
import SearchBar from '../components/SearchBar'; // React component
import Comments from '../components/Comments'; // React component
---








Enable content collections with static generation

Astro generates static HTML at build time by default, which means near-zero TTFB when deployed to a CDN. Use content collections with getStaticPaths() to pre-render all pages. Avoid using server output mode unless you specifically need server-side rendering -- it adds server processing time that directly increases LCP.

Astro -- astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  // 'static' is the default -- pre-renders all pages
  output: 'static',

  // Enable image optimization
  image: {
    service: { entrypoint: 'astro/assets/services/sharp' },
  },

  // Build optimizations
  build: {
    inlineStylesheets: 'auto',  // Inline small CSS
  },

  // Vercel adapter for edge deployment
  // adapter: vercel({ edgeMiddleware: true }),
});

Optimize font loading with font-display and subsetting

Self-host your fonts and use font-display: swap to show fallback text immediately while custom fonts load. Subset fonts to include only the characters you need. In Astro, define font-face rules in a global CSS file and preload the WOFF2 files in your layout's head.

CSS -- Global font setup
/* src/styles/global.css */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;  /* Show fallback immediately */
  unicode-range: U+0000-00FF;  /* Latin subset only */
}

/* Size-adjust fallback to minimize layout shift */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  ascent-override: 90.49%;
  descent-override: 22.56%;
  line-gap-override: 0%;
  size-adjust: 107.64%;
}

body {
  font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
}

Quick checklist

  • Hero image uses <Image /> from astro:assets with loading="eager"
  • Critical images have fetchpriority="high"
  • Fonts preloaded in layout head with font-display: swap
  • No client:* directives on above-the-fold components
  • Site uses output: 'static' mode (default)
  • CSS inlined automatically via build.inlineStylesheets
  • Deployed to CDN edge (Vercel, Netlify, Cloudflare Pages)

Frequently asked questions

Astro sites routinely achieve LCP under 1.2 seconds because they ship zero JavaScript by default and generate static HTML at build time. With optimized images via astro:assets and edge deployment, sub-second LCP is achievable. If your Astro site has LCP above 2 seconds, check for unoptimized images, render-blocking scripts added via client directives, or slow server response from non-edge hosting.

Yes, significantly. Because Astro only sends JavaScript for components explicitly marked with client:* directives, the LCP content area is pure HTML and CSS with no hydration delay. This is fundamentally different from React-based frameworks where the entire page hydrates. The key is ensuring your above-the-fold content uses Astro components (not framework components with client directives).

Use SSG (static) mode for best LCP. Static pages serve directly from CDN edge nodes with near-zero TTFB. Only switch to SSR mode if you need per-request personalization or real-time data. For content that updates periodically, use hybrid mode where most pages are static and only specific routes use server rendering.

Astro's View Transitions API enables smooth page-to-page animations without a full-page reload. For the initial page load, view transitions have minimal LCP impact since they primarily affect subsequent navigations. However, ensure the transition:animate directive on your LCP element does not add unnecessary delay. Use transition:animate='none' on hero images to avoid animation overhead.

In most benchmarks, Astro achieves better LCP scores than Next.js because it ships zero client-side JavaScript by default. CrUX data shows Astro sites have an 82% CWV pass rate versus 68% for Next.js. However, Astro is best suited for content-heavy sites, while Next.js offers more flexibility for highly interactive applications that need client-side state management.

Google rates LCP as 'good' when it is under 2.5 seconds at the 75th percentile. For Astro applications specifically, aim for under 2.0 seconds. Measure with field data from Chrome User Experience Report (CrUX) through PageSpeed Insights, as lab tests may not reflect real-user experience with third-party scripts and varying network conditions.

Continue learning