Introduction
Largest Contentful Paint (LCP) measures when the largest visual element above the fold (typically a hero banner image, headline text block, or video poster) becomes fully rendered.
A common pitfall in single-page React applications is requiring the browser to download, parse, and execute hundreds of kilobytes of JavaScript before the hero element can even begin loading. Start with the HTML, CSS, font, and image that make the first viewport useful; every other byte should earn its place.
The examples below are starting points, not universal defaults. fetchpriority="high" belongs on the actual LCP candidate—not every image—and preloading a font can hurt if that font is not used above the fold.
1. Optimizing Asset Fetch Priority
Tell the browser explicitly which resources are required for initial render using fetchpriority="high" and preloading critical web fonts:
<!-- Preload critical web font with display=swap -->
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<!-- Hero image with explicit high priority -->
<img
src="/hero-banner.webp"
alt="Platform Preview"
fetchpriority="high"
loading="eager"
width="1200"
height="630"
/>2. Lazy Loading Heavy Offscreen Components
Heavy components below the fold—such as interactive charts, code editors, or complex video players—should never bloat your initial JavaScript bundle.
Use Next.js dynamic() or React lazy() with Suspense:
import dynamic from "next/dynamic";
// Dynamic import with SSR disabled for browser-only heavy widget
const HeavyAnalyticsChart = dynamic(
() => import("@/components/HeavyAnalyticsChart"),
{
loading: () => <div className="skeleton-chart" />,
ssr: false,
}
);
export function DashboardPage() {
return (
<main>
<HeroHeader />
<Suspense fallback={<div className="skeleton" />}>
<HeavyAnalyticsChart />
</Suspense>
</main>
);
}3. Replacing Heavy npm Packages with Lightweight Alternatives
Replacing legacy npm packages with modern, tree-shakeable equivalents reduces bundle sizes drastically:
| Heavy Package | Size | Modern Alternative | Size | Saved |
|---|---|---|---|---|
moment.js | 280 kB | date-fns / native Intl | 2-8 kB | ~97% |
lodash | 70 kB | lodash-es / native ES2024 | 1-4 kB | ~95% |
axios | 32 kB | Native fetch() | 0 kB | 100% |
4. Key Metrics to Target
- LCP Target: Under 1.8s on 4G mobile networks.
- Initial JS Bundle: Under 100 kB gzipped for the critical initial chunk.
- CLS (Cumulative Layout Shift): 0.00 by reserving explicit
widthandheightdimensions on images and dynamic containers.
5. A Release Checklist
- Confirm the actual LCP element in a trace; optimize that element first.
- Serve responsive images in modern formats instead of shipping a desktop-sized hero to a phone.
- Test a cold load on a throttled mobile profile and a warm navigation separately.
- Recheck real-user p75 LCP after deployment. A lab improvement matters only when field data agrees.