← Back to Guides
9 min readIntermediate
Share

Performance Optimization for AI-Generated Code

How to identify and fix performance problems in vibecoded apps — from React re-renders to bundle size to API latency.

Performance Optimization for AI-Generated Code

AI-generated code works. But "works" and "works fast" are different things. Here's how to find and fix the performance problems that AI introduces.

The Common Performance Pitfalls

1. Unnecessary Re-renders

This is the #1 performance issue in AI-generated React code. AI doesn't think about render cycles.

The problem:

// AI generates this — a new object on every render
function UserList({ users }) {
  const sortedUsers = users.sort((a, b) => a.name.localeCompare(b.name));
  // This creates a new reference every render, causing child re-renders
  return <List items={sortedUsers} />;
}

The fix:

function UserList({ users }) {
  const sortedUsers = useMemo(
    () => [...users].sort((a, b) => a.name.localeCompare(b.name)),
    [users]
  );
  return <List items={sortedUsers} />;
}

When to use useMemo:

  • Expensive computations (sorting, filtering large arrays)
  • Object/array references passed as props to memoized children
  • Values used in dependency arrays of other hooks

When NOT to use useMemo:

  • Simple string concatenation or arithmetic
  • Primitives (numbers, strings, booleans) — they compare by value anyway

2. Bundle Size Bloat

AI loves importing full libraries when you need one function:

// AI generates this — imports ALL of lodash (70KB+)
import _ from "lodash";
const sorted = _.sortBy(users, "name");

// Better — import just what you need (4KB)
import sortBy from "lodash/sortBy";

// Best — write it yourself (0KB)
const sorted = [...users].sort((a, b) => a.name.localeCompare(b.name));

Check your bundle size:

npx next build
# Look for "First Load JS" in the output
# Anything over 100KB per page should be investigated

3. Missing Code Splitting

AI generates everything in one file. Real apps should split:

// AI generates this — loads the chart library even if user never opens the tab
import { BarChart } from "recharts";

// Better — load it only when needed
import dynamic from "next/dynamic";
const BarChart = dynamic(() => import("recharts").then((m) => m.BarChart), {
  loading: () => <div className="h-64 bg-neutral-800 animate-pulse rounded-xl" />,
});

Use dynamic imports for:

  • Charts and visualization libraries
  • Rich text editors
  • Code editors
  • Maps
  • Any component only visible after user interaction

4. N+1 API Calls

AI-generated code often fetches data in loops:

// BAD — N+1 queries
const users = await db.query("SELECT * FROM users");
for (const user of users) {
  user.posts = await db.query("SELECT * FROM posts WHERE user_id = ?", [user.id]);
}

// GOOD — single query with join
const users = await db.query(`
  SELECT u.*, json_agg(p.*) as posts
  FROM users u
  LEFT JOIN posts p ON p.user_id = u.id
  GROUP BY u.id
`);

5. Unoptimized Images

AI generates <img> tags. Next.js has a purpose-built Image component:

// AI generates
<img src="/hero.jpg" alt="Hero" />

// Better
import Image from "next/image";
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />

The Next.js Image component automatically:

  • Serves WebP/AVIF formats
  • Lazy loads images below the fold
  • Prevents layout shift with width/height
  • Serves responsive sizes

Measuring Performance

Core Web Vitals

The metrics that matter:

  • LCP (Largest Contentful Paint): < 2.5s — how fast the main content loads
  • FID (First Input Delay): < 100ms — how fast the page responds to interaction
  • CLS (Cumulative Layout Shift): < 0.1 — how much the layout jumps around

Tools

  • Lighthouse (built into Chrome DevTools) — run it on every page
  • React DevTools Profiler — shows which components re-render and why
  • next build output — shows bundle sizes per page

Quick Wins

If you want to improve performance right now:

  1. Add loading="lazy" to images below the fold
  2. Add priority to your hero image / LCP element
  3. Move client-side data fetching to server components where possible
  4. Replace useEffect + fetch with server-side data fetching in Next.js
  5. Add Suspense boundaries around slow-loading components

The Performance Checklist

Before deploying, check:

  • [ ] Lighthouse score > 90 on mobile
  • [ ] First Load JS < 100KB per page
  • [ ] No unnecessary "use client" directives
  • [ ] Images use Next.js Image component
  • [ ] Heavy libraries are dynamically imported
  • [ ] API calls are batched (no N+1)
  • [ ] Lists have proper key props
  • [ ] Expensive computations are memoized

AI can generate fast code — but only if you prompt for it and verify the results.

Get the good stuff

New tools and posts, occasionally. No spam.