Largest Contentful Paint (LCP) is the time until the biggest thing above the fold, usually the hero image or headline, appears on screen. It is a Core Web Vital and feeds into Google ranking. More importantly, it is what a visitor feels as "this site is slow". The target is 2.5 seconds or less on mobile.
AI-built apps tend to fail LCP for the same few reasons. Here is how to diagnose and fix each one.
Step 1: measure and find the LCP element
Run your URL through PageSpeed Insights on the mobile tab, or use Lighthouse in Chrome DevTools. Under "Largest Contentful Paint element" you will see which element it is and how its time breaks down:
- TTFB: the server is slow to respond.
- Load delay: the browser found the resource late, typically because JavaScript had to run first.
- Load time: the resource itself is too heavy.
- Render delay: it loaded but couldn't paint because of blocking CSS, JS or fonts.
Fix the biggest slice first.
Step 2: fix the hero image (most common)
- Compress and resize. A 4000px PNG hero is often 3–5 MB. Export WebP or AVIF at the size it is displayed (around 1600px for desktop, smaller for mobile using
srcset). Squoosh orsharpmake this easy. - Don't lazy-load it. AI tools often add
loading="lazy"to every image. For the LCP image, remove it and addfetchpriority="high". - Make it discoverable in HTML. If the image only appears after React renders, the browser can't start downloading it early. Preload it:
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<img src="/hero.webp" width="1600" height="900" alt="…" fetchpriority="high">
- In Next.js, use
<Image priority />for the hero. - Avoid CSS background images for the hero. The browser finds them late.
Step 3: reduce client-side rendering delay
Lovable, Bolt and many Cursor projects are Vite + React single-page apps. The HTML is an empty <div id="root">, so nothing paints until the whole bundle downloads, parses and runs. Options, from easiest to most work:
- Code-split routes:
const Dashboard = lazy(() => import('./Dashboard'))so the landing page doesn't ship the whole app. - Trim dependencies: run
npx vite-bundle-visualizer. Common offenders are full icon packs, moment.js, several chart libraries and syntax highlighters. Import only the icons you use. - Don't block the hero on data: render the headline and hero immediately and fetch user data afterwards. A spinner as the first paint means the spinner is not your LCP but your real content is late.
- Prerender the landing page: use static HTML for marketing pages, or move to a framework with SSR or SSG (Next.js, Astro, React Router framework mode) so content is in the initial HTML.
Step 4: fonts
- Use
font-display: swapso text shows immediately in a fallback font. - Self-host fonts, or at least
preconnecttofonts.gstatic.com. Load only the weights you use, typically two. - Prefer WOFF2 and preload the one font your headline uses.
Step 5: render-blocking CSS and third-party scripts
- Load analytics, chat widgets and tag managers with
asyncordefer, or after the page loads. - Large CSS frameworks: Tailwind already purges unused classes in production. Make sure you are deploying the production build, not the dev server.
- Remove unused third-party embeds on the landing page, such as YouTube iframes. Use a click-to-load thumbnail instead.
Step 6: server response and caching
- Serve static assets from a CDN with long cache headers. Hashed filenames from Vite and Next.js can use
Cache-Control: public, max-age=31536000, immutable. - If TTFB is over about 800 ms, check for slow server-side data fetching, cold starts, or a database far from your function region. Put functions in the same region as your database.
- Enable compression (Brotli or gzip). Vercel and Netlify do this automatically.
Step 7: re-measure
Lab scores vary from run to run, so test two or three times. Real-user LCP from the Chrome UX Report (shown at the top of PageSpeed Insights once you have traffic) is what Google actually uses.
ShipCheck runs a mobile PageSpeed check as part of its free scan, alongside security headers and leaked-key checks, so you can watch performance and security together before you launch. For the rest of the pre-launch list, see the vibe-coded app launch checklist.