Back to Blog
Production GuideSan Francisco, CA

Part 3: 6 Performance Pitfalls That Kill Your AI-Generated App

Jan 12, 20269 min read
Part 3: 6 Performance Pitfalls That Kill Your AI-Generated App

⚑ Speed Is a Feature

Here's a brutal truth:

53% of mobile users leave if a page takes longer than 3 seconds to load.

Your AI-generated prototype runs fine on your MacBook Pro with gigabit internet. But in the real world? Users are on spotty 4G, old phones, and impatient.

In Part 1 we covered the deployment checklist. In Part 2, security. Now let's make your app fast.


🐌 The 6 Performance Killers

1. Unoptimized Images

The Problem: AI tools often use placeholder images or generate code that loads full-resolution images directly.

<!-- ❌ 4MB PNG loaded on a mobile device --> <img src="/hero-background.png" />

The Impact:

  • A single 4MB image on 3G takes 30+ seconds
  • Users see a blank screen and leave
  • Your hosting bill explodes

The Fix:

// βœ… Use Next.js Image component (auto-optimizes) import Image from 'next/image'; <Image src="/hero-background.png" alt="Hero" width={1200} height={600} priority // Loads immediately for above-the-fold />;

Quick Wins:

  • Convert PNGs to WebP (80% smaller)
  • Use loading="lazy" for below-fold images
  • Serve different sizes for mobile vs desktop

2. Loading Everything Upfront

The Problem: AI-generated code often imports every component at the top of the file.

// ❌ All these load immediately, even if user never sees them import HeavyChart from './HeavyChart'; import AdminPanel from './AdminPanel'; import VideoPlayer from './VideoPlayer';

The Impact:

  • Initial JavaScript bundle is 2MB+
  • First paint takes 5+ seconds
  • Users on slow connections see a white screen

The Fix:

// βœ… Lazy load components only when needed import { lazy, Suspense } from 'react'; const HeavyChart = lazy(() => import('./HeavyChart')); const AdminPanel = lazy(() => import('./AdminPanel')); function Dashboard() { return ( <Suspense fallback={<div>Loading...</div>}> {showChart && <HeavyChart />} </Suspense> ); }

3. N+1 Database Queries

The Problem: Fetching a list, then making a separate query for each item.

// ❌ 1 query for posts + 100 queries for authors = 101 queries const posts = await supabase.from('posts').select('*'); for (const post of posts) { const author = await supabase .from('users') .select('name') .eq('id', post.author_id); post.author = author; }

The Impact:

  • 100 posts = 101 database round trips
  • Each round trip adds 50-200ms latency
  • Page load time: 5-20 seconds

The Fix:

// βœ… One query with joins const { data: posts } = await supabase.from('posts').select(` *, author:users(name, avatar_url) `);

Result: 101 queries β†’ 1 query = 100x faster


4. No Caching Strategy

The Problem: Every page load fetches fresh data from the database, even when nothing has changed.

// ❌ Fetches from DB on every single page view useEffect(() => { fetchUserProfile(); }, []);

The Impact:

  • Unnecessary database load
  • Slower page transitions
  • Higher hosting costs

The Fix:

// βœ… Use React Query or SWR for smart caching import { useQuery } from '@tanstack/react-query'; function Profile() { const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUserProfile(userId), staleTime: 5 * 60 * 1000, // Cache for 5 minutes }); }

Caching Strategies:

Data TypeCache DurationExample
User profile5-15 minutesName, avatar
Product catalog1-4 hoursPrices, descriptions
Static content24+ hoursBlog posts, FAQs

5. Render-Blocking Resources

The Problem: External fonts and scripts blocking the initial render.

<!-- ❌ Page waits for Google Fonts before showing ANYTHING --> <link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />

The Impact:

  • Flash of Invisible Text (FOIT)
  • 1-3 seconds of blank screen
  • Poor Core Web Vitals scores

The Fix:

<!-- βœ… Load fonts asynchronously with fallback --> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter&display=swap" />
/* βœ… Use font-display: swap */ @font-face { font-family: 'Inter'; font-display: swap; /* Shows fallback font immediately */ src: url('/fonts/inter.woff2') format('woff2'); }

6. No Loading States

The Problem: Users click a button and... nothing happens. No feedback.

// ❌ User has no idea if the click worked <button onClick={submitForm}>Submit</button>

The Impact:

  • Users click multiple times (duplicate submissions)
  • Perceived performance is terrible
  • Frustration β†’ churn

The Fix:

// βœ… Clear loading feedback const [isLoading, setIsLoading] = useState(false); <button onClick={async () => { setIsLoading(true); await submitForm(); setIsLoading(false); }} disabled={isLoading} > {isLoading ? <span className="animate-spin">⏳</span> : 'Submit'} </button>;

Pro tip: Add skeleton loaders for content that takes time to load.


πŸ“Š Measure Before You Optimize

Don't guessβ€”measure. Use these free tools:

Google Lighthouse

Built into Chrome DevTools. Scores 0-100 on:

  • Performance
  • Accessibility
  • Best Practices
  • SEO

Target: 80+ on all metrics

Web Vitals

The metrics Google uses for ranking:

MetricTargetWhat It Measures
LCP (Largest Contentful Paint)< 2.5sMain content load time
FID (First Input Delay)< 100msResponsiveness
CLS (Cumulative Layout Shift)< 0.1Visual stability

Vercel Analytics

If you're on Vercel, enable Real Experience Score for actual user data.


βœ… The Performance Checklist

Images

  • All images converted to WebP
  • Lazy loading for below-fold images
  • Responsive images (srcset) for different screen sizes

JavaScript

  • Bundle size < 200KB (gzipped)
  • Lazy loading for heavy components
  • No unused dependencies in bundle

Database

  • No N+1 queries (use joins)
  • Caching layer implemented (React Query/SWR)
  • Database indexes on frequently queried columns

Loading UX

  • Loading states for all async actions
  • Skeleton loaders for content areas
  • Optimistic updates where appropriate

🏎️ Quick Wins Summary

IssueFixImpact
Large imagesConvert to WebP50-80% smaller
Bundle sizeLazy load2-3x faster first load
N+1 queriesUse joins10-100x faster
No cachingAdd React QueryInstant page transitions
FOITfont-display: swapNo blank text
No feedbackLoading statesBetter perceived speed


Performance Is Our Specialty

We specialize in optimizing AI-generated apps for performance. Our goal: 3-5x faster load times and 90+ Lighthouse scores.

Speed Up Your App

πŸ“š Series Navigation

PartTitleStatus
1The 7-Point Deployment Checklistβœ…
2Web App Security Basicsβœ…
36 Performance Pitfalls (This Post)βœ…
4Database Design Guide for Non-DevelopersπŸ”œ Coming Soon

Next up: Database designβ€”schemas, migrations, and making sure your data structure doesn't become a nightmare.

Previous: Part 2: Web App Security Basics


*Slow app? We can help. Reach out: hello@shiptheproduct.dev*