Why Your Vibe-Coded App Scores Low on Lighthouse (And How to Fix It) - VibeDoctor 
← All Articles ⚡ Performance Medium

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 routinely score below 50 on Lighthouse because they ship large JavaScript bundles, unoptimized images, layout-shifting components, and no server-side rendering strategy. Lighthouse measures five Core Web Vitals 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 confirmed in 2021 that Core Web Vitals are a direct ranking factor in search results. A page that scores poorly on LCP and CLS is at a measurable disadvantage against competitors with optimized sites, all else being equal.

But the business impact goes beyond SEO. Google's own research shows that 53% of mobile users abandon a page that takes longer than 3 seconds to load. Deloitte found that improving mobile load times by 0.1 seconds increased conversions by 8% for retail sites and 10% 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 43% of desktop pages pass Core Web Vitals. For mobile, the number drops to 40%. 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 Core Web Vitals: 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) and Time to Interactive (TTI) (when the page becomes fully interactive). The weighted scoring formula means LCP and TBT have the most impact on your overall score - fixing those two metrics typically moves the needle the most.

LH-001: 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:

// ❌ 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 
{data ? : null}
; }
// ✅ GOOD - Server-side rendering, HTML arrives pre-populated
// app/page.tsx (Next.js App Router - Server Component by default)
async function HomePage() {
  const data = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 }
  }).then(r => r.json());
  return ;
}

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-002: Largest Contentful Paint - The Biggest Offender

LCP is the most impactful Core Web Vital. 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


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

Hero banner

The Next.js Image component automatically serves WebP/AVIF formats, lazy loads off-screen images, prevents CLS with explicit dimensions, and the priority prop 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 improve LCP by 1-2 seconds.

LH-003: 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 frequently exceed 1,000ms TBT because they ship large, uncode-split bundles.

// ❌ 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: () =>  }
);
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-004 and 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
Product

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

// ✅ GOOD - Explicit aspect ratio reserves space before image loads
Product
// ✅ GOOD - Reserve space for dynamic content with min-height
{showBanner && }

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 (vibedoctor.io) automatically run Lighthouse against your live site and flag specific LH-001 through LH-005 findings with the exact file paths and 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 multiple audits automatically.

Does Tailwind CSS affect Lighthouse scores?

With PurgeCSS configured correctly, Tailwind ships only the CSS classes you actually use - typically 5-15 KB. Without purging, it ships 3 MB of CSS which severely hurts FCP and TBT. Vercel deploys Next.js with Tailwind purging enabled by default. Check that your tailwind.config.js has the content array pointing to your component files.

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 uses Core Web Vitals (LCP, CLS, and now INP - Interaction to Next Paint) as a ranking signal in the Page Experience algorithm. 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.

Scan your codebase for this issue - free

VibeDoctor checks for LH-001, LH-002, LH-003, LH-004, LH-005 and 128 other issues across 15 diagnostic areas.

SCAN MY APP →
← Back to all articles View all 129+ checks →