← All Articles ⚡ Performance Medium By Nishant Kumar Verma Updated

Why Your Vibe-Coded App Scores Low on Lighthouse (And How to Fix It)

AI-generated apps routinely score below 50 on Lighthouse. Learn what FCP, LCP, TBT, and CLS mean and how to improve each metric.

LH-001 LH-002 LH-003 LH-004 LH-005

Quick Answer

Vibe-coded apps built with Bolt, Lovable, v0, and Cursor often score below 50 on Lighthouse because they ship large JavaScript bundles, unoptimized images, layout-shifting components, and no server-side rendering strategy. Lighthouse builds its performance score from five metrics: FCP, LCP, TBT, CLS, and Speed Index. Each one has a specific fix that can move your score from red to green without rewriting your app.

Why Lighthouse Scores Matter (More Than You Think)

A Lighthouse score isn't just a vanity metric. Google's page experience update brought Core Web Vitals into search ranking in 2021, and Google's documentation still says "Core Web Vitals are used by our ranking systems." A page that scores poorly on LCP and CLS can be at a disadvantage against competitors with optimized sites, all else being equal.

But the business impact goes beyond SEO. Google's 2016 mobile speed research shows that 53% of mobile site visits are abandoned if pages take longer than 3 seconds to load. A Deloitte study commissioned by Google, Milliseconds Make Millions, found that a 0.1 second improvement in mobile site speed increased conversions by 8.4% for retail sites and 10.1% for travel sites. A slow app built on Bolt or Lovable isn't just ranking lower - it's converting fewer visitors into users.

According to the HTTP Archive Web Almanac 2024, only 54% of desktop sites pass Core Web Vitals. For mobile sites, it is 43%. AI-generated apps tend to cluster at the lower end of this distribution because they optimize for functionality at build time, not performance at runtime.

The 4 Key Lighthouse Metrics: Thresholds Explained

Metric What It Measures Good Needs Improvement Poor
FCP - First Contentful Paint Time until first text or image appears ≤ 1.8s 1.8s – 3.0s > 3.0s
LCP - Largest Contentful Paint Time until the largest visible element loads ≤ 2.5s 2.5s – 4.0s > 4.0s
TBT - Total Blocking Time JS that blocks the main thread after FCP ≤ 200ms 200ms – 600ms > 600ms
CLS - Cumulative Layout Shift Visual instability as page elements move ≤ 0.1 0.1 – 0.25 > 0.25

Lighthouse also measures Speed Index (how quickly page contents are visually populated). Time to Interactive (TTI) was removed from Lighthouse in version 10. In the scoring formula, TBT carries 30% of the score, LCP and CLS 25% each, and FCP and Speed Index 10% each - so fixing TBT and LCP typically moves the needle the most. Of these metrics, only LCP and CLS are Core Web Vitals. The third, Interaction to Next Paint (INP), needs real user input, so a page-load Lighthouse run does not measure it.

LH-002: First Contentful Paint - Too Slow

FCP measures how long a user waits before seeing anything on screen. In AI-generated apps, FCP is typically slow because:

  • The entire React application bundle must download and parse before rendering begins
  • Large CSS bundles (MUI, Tailwind with all utilities) block the render path
  • No critical CSS is inlined - the browser waits for an external stylesheet
// ❌ BAD - Client-only rendering, nothing until JS loads
// pages/index.js (Next.js without SSR)
export default function Home() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch('/api/products').then(r => r.json()).then(setData);
  }, []);
  return <div>{data ? <ProductList items={data} /> : null}</div>;
}
// ✅ GOOD - Server-side rendering, HTML arrives pre-populated
// app/page.tsx (Next.js App Router - Server Component by default)
export default async function HomePage() {
  const data = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 }
  }).then(r => r.json());
  return <ProductList items={data} />;
}

Switching from useEffect data fetching to Next.js Server Components or getServerSideProps means the HTML arrives from the server already populated with content. The browser renders immediately rather than waiting for JavaScript to execute and fetch data.

LH-003: Largest Contentful Paint - The Biggest Offender

LCP is one of the most heavily weighted metrics. It measures when the largest element (usually a hero image or heading) becomes visible. The two most common LCP killers in AI-generated apps are unoptimized images and render-blocking resources.

// ❌ BAD - Unoptimized image, no size hints, no priority
<img src="/hero-banner.jpg" className="hero-image" />

// ✅ GOOD - Next.js Image component with preload and dimensions
import Image from 'next/image';

<Image
  src="/hero-banner.jpg"
  alt="Hero banner"
  width={1200}
  height={600}
  preload={true}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQ..."
/>

The Next.js Image component automatically serves WebP (and AVIF, if you enable it in next.config), lazy loads off-screen images, prevents CLS with explicit dimensions, and the preload prop (called priority before Next.js 16) preloads the above-the-fold hero image. Bolt and v0 often generate plain <img> tags instead. Switching to next/image for hero images alone can noticeably improve LCP.

LH-004: Total Blocking Time - JavaScript Overload

TBT measures how much time the main thread spends executing long JavaScript tasks after FCP. Every millisecond your browser spends parsing and executing JavaScript is time it can't respond to user input. The threshold is 200ms total. AI-generated apps can exceed 1,000ms TBT when they ship large bundles with no code splitting.

// ❌ BAD - All components loaded upfront, large initial bundle
import HeavyDashboard from '@/components/HeavyDashboard';
import DataVisualization from '@/components/DataVisualization';
import AdminPanel from '@/components/AdminPanel';
// ✅ GOOD - Code split: only load what's needed for the route
import dynamic from 'next/dynamic';

const HeavyDashboard = dynamic(() => import('@/components/HeavyDashboard'));
const DataVisualization = dynamic(
  () => import('@/components/DataVisualization'),
  { loading: () => <Skeleton /> }
);
const AdminPanel = dynamic(() => import('@/components/AdminPanel'));

Next.js's dynamic() splits each component into its own JavaScript chunk that only downloads when the component is rendered. For an admin panel that's only shown to 5% of users, not splitting it means 95% of your visitors download code they never execute.

LH-005: CLS - Layout Shift From No Dimensions

Cumulative Layout Shift is the most frustrating UX metric. It measures how much page content shifts after initial render - like a button that moves just as you're about to click it. AI-generated apps commonly cause CLS in two ways: images without explicit dimensions, and dynamically injected content above existing content.

// ❌ BAD - No dimensions causes layout shift when image loads
<img src="/product-photo.jpg" alt="Product" />

// ❌ BAD - Dynamic banner injects above content, pushing everything down
useEffect(() => {
  if (showBanner) {
    setBannerContent(<PromoBanner />);
  }
}, []);

// ✅ GOOD - Explicit aspect ratio reserves space before image loads
<div style={{ aspectRatio: '16/9', position: 'relative' }}>
  <Image src="/product-photo.jpg" alt="Product" fill />
</div>
// ✅ GOOD - Reserve space for dynamic content with min-height
<div style={{ minHeight: '60px' }}>
  {showBanner && <PromoBanner />}
</div>

How to Run Lighthouse and Track Improvements

# Run Lighthouse from the command line (install globally)
npm install -g lighthouse

# Run against your local dev server
lighthouse http://localhost:3000 --output html --output-path report.html

# Run against your production URL
lighthouse https://yourapp.vercel.app --output json --output-path report.json

# Run in CI (headless Chrome)
lighthouse https://yourapp.vercel.app \
  --chrome-flags="--headless" \
  --output json \
  --output-path lighthouse.json

Tools like VibeDoctor's Vibe Check automatically run Lighthouse against your live site and report LH-001 through LH-005 (the performance score, FCP, LCP, TBT and CLS) plus every failed Lighthouse performance audit. Lighthouse sees the live page, not your repo, so use the audit names to find the components responsible. Free to sign up.

FAQ

What Lighthouse score should I aim for?

90+ is the green zone for all metrics. 70-89 is acceptable for complex applications. Below 70 means users are having a noticeably degraded experience. For landing pages and marketing sites, aim for 90+. For complex dashboards with heavy data visualization, 75+ is a realistic target with proper code splitting.

Why does my Lighthouse score vary between runs?

Lighthouse simulates a mid-range mobile device on a throttled connection. Network conditions, server response time variability, and background processes on your testing machine all affect results. Always run 3-5 times and average the scores, or use Lighthouse CI, which runs Lighthouse 3 times per URL by default.

Does Tailwind CSS affect Lighthouse scores?

Since v3, Tailwind generates only the CSS classes you actually use, which Tailwind says usually means less than 10 KB. Tailwind v2 without its purge option shipped a development build of about 3.6 MB, which severely hurts FCP and TBT. On v3, check that your tailwind.config.js has the content array pointing to your component files; v4 finds your source files automatically.

Will upgrading to Next.js App Router improve my score?

Often yes. App Router components are Server Components by default, which means zero client-side JavaScript for components that don't need interactivity. This directly reduces TBT and improves FCP. The caveat is that App Router has a learning curve and some patterns from Pages Router don't translate directly.

How does a low Lighthouse score affect Google rankings?

Google's ranking systems use Core Web Vitals (LCP, CLS, and INP - Interaction to Next Paint) as one of many signals; Google says there is no single page experience signal. Poor scores don't cause immediate de-indexing, but they put you at a disadvantage in competitive searches where competing pages have similar content quality but better performance. The SEO impact is greatest for highly competitive queries.

Diagnose your codebase - free

VibeDoctor checks for LH-001, LH-002, LH-003, LH-004, LH-005 and 148 other issues across 21 diagnostic areas - security, performance, code quality, and more.

SCAN MY APP →