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 Type | Cache Duration | Example |
|---|---|---|
| User profile | 5-15 minutes | Name, avatar |
| Product catalog | 1-4 hours | Prices, descriptions |
| Static content | 24+ hours | Blog 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:
| Metric | Target | What It Measures |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | Main content load time |
| FID (First Input Delay) | < 100ms | Responsiveness |
| CLS (Cumulative Layout Shift) | < 0.1 | Visual 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
| Issue | Fix | Impact |
|---|---|---|
| Large images | Convert to WebP | 50-80% smaller |
| Bundle size | Lazy load | 2-3x faster first load |
| N+1 queries | Use joins | 10-100x faster |
| No caching | Add React Query | Instant page transitions |
| FOIT | font-display: swap | No blank text |
| No feedback | Loading states | Better 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
| Part | Title | Status |
|---|---|---|
| 1 | The 7-Point Deployment Checklist | β |
| 2 | Web App Security Basics | β |
| 3 | 6 Performance Pitfalls (This Post) | β |
| 4 | Database 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*