Advanced20 min1 prerequisite

Identify and fix performance issues in AI-generated code with systematic optimization techniques.

Performance Optimization

Learn to identify and fix performance issues in AI-generated code, from client-side rendering to database queries.

Why AI Code Needs Optimization

Common AI Performance Issues

Terminal
AI tends to generate:
├── Correct but unoptimized code
├── Over-fetching data
├── Unnecessary re-renders
├── N+1 query patterns
├── Missing caching
└── Blocking operations

Example: Unoptimized vs Optimized

Terminal
// AI-generated (works but slow)
async function getUsers() {
  const users = await db.users.findMany()
  return users.map(user => ({
    ...user,
    posts: await db.posts.findMany({ where: { userId: user.id } }),
    comments: await db.comments.findMany({ where: { userId: user.id } })
  }))
}

// Optimized (batched queries)
async function getUsers() {
  const users = await db.users.findMany({
    include: {
      posts: true,
      comments: true
    }
  })
  return users
}

Client-Side Performance

Identifying React Re-renders

Terminal
// Install React DevTools Profiler
// Or add debug logging:

import { useEffect, useRef } from 'react'

function useRenderCount(componentName: string) {
  const renderCount = useRef(0)
  renderCount.current++

  useEffect(() => {
    console.log(`${componentName} rendered ${renderCount.current} times`)
  })
}

// Usage in component
function ProductList({ products }) {
  useRenderCount('ProductList')
  // ...
}

Common Re-render Fixes

Terminal
// Problem: New object/array reference every render
function Parent() {
  return <Child options={{ sort: 'name' }} /> //  New object each render
}

// Fix: Memoize or move outside
const OPTIONS = { sort: 'name' }
function Parent() {
  return <Child options={OPTIONS} /> //  Stable reference
}

// Or with useMemo for dynamic values
function Parent({ sortField }) {
  const options = useMemo(() => ({ sort: sortField }), [sortField])
  return <Child options={options} />
}
Terminal
// Problem: Inline function props
function Parent() {
  return <Button onClick={() => handleClick()} /> //  New function each render
}

// Fix: useCallback
function Parent() {
  const handleButtonClick = useCallback(() => {
    handleClick()
  }, [])
  return <Button onClick={handleButtonClick} /> //  Stable function
}

Optimizing Lists

Terminal
// Problem: AI often generates without keys or with index keys
{items.map((item, index) => (
  <Item key={index} item={item} /> //  Index as key
))}

// Fix: Use stable unique identifiers
{items.map((item) => (
  <Item key={item.id} item={item} /> //  Stable ID
))}
Terminal
// Problem: Large lists rendering entirely
function ProductList({ products }) {
  return (
    <div>
      {products.map(product => <ProductCard product={product} />)}
    </div>
  )
}

// Fix: Virtualization for large lists
import { useVirtualizer } from '@tanstack/react-virtual'

function ProductList({ products }) {
  const parentRef = useRef(null)

  const virtualizer = useVirtualizer({
    count: products.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 100,
  })

  return (
    <div ref={parentRef} className="h-[600px] overflow-auto">
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <ProductCard
            key={products[virtualItem.index].id}
            product={products[virtualItem.index]}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          />
        ))}
      </div>
    </div>
  )
}

Server-Side Performance

Database Query Optimization

Terminal
// Problem: N+1 queries
async function getOrdersWithItems() {
  const orders = await db.orders.findMany()

  //  One query per order
  for (const order of orders) {
    order.items = await db.orderItems.findMany({
      where: { orderId: order.id }
    })
  }

  return orders
}

// Fix: Single query with join
async function getOrdersWithItems() {
  return db.orders.findMany({
    include: {
      items: true  //  Single query with join
    }
  })
}

Supabase Query Optimization

Terminal
// Problem: Over-fetching
const { data } = await supabase
  .from('users')
  .select('*')  //  Fetches all columns

// Fix: Select only needed columns
const { data } = await supabase
  .from('users')
  .select('id, name, email')  //  Only needed columns
Terminal
// Problem: No pagination
const { data } = await supabase
  .from('products')
  .select('*')  //  Fetches all products

// Fix: Pagination
const { data, count } = await supabase
  .from('products')
  .select('*', { count: 'exact' })
  .range(0, 19)  //  First 20 products

Caching Strategies

Terminal
// Next.js fetch caching
async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 3600 }  // Cache for 1 hour
  })
  return res.json()
}
Terminal
// React cache for deduplication
import { cache } from 'react'

export const getUser = cache(async (id: string) => {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
})

// Called multiple times but only fetches once per request
await getUser('123')
await getUser('123')  // Uses cached result
Terminal
// unstable_cache for database queries
import { unstable_cache } from 'next/cache'

const getCachedProducts = unstable_cache(
  async () => {
    return db.products.findMany()
  },
  ['products'],  // Cache key
  { revalidate: 3600 }  // Revalidate every hour
)

Bundle Size Optimization

Analyze Bundle

Terminal
# Install bundle analyzer
npm install @next/bundle-analyzer

# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})

# Run analysis
ANALYZE=true npm run build

Dynamic Imports

Terminal
// Problem: Large component loaded immediately
import HeavyChart from '@/components/HeavyChart'

// Fix: Lazy load
import dynamic from 'next/dynamic'

const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <ChartSkeleton />,
  ssr: false  // If chart doesn't need SSR
})

Tree Shaking

Terminal
// Problem: Importing entire library
import _ from 'lodash'  //  Imports all of lodash
_.debounce(fn, 300)

// Fix: Import specific function
import debounce from 'lodash/debounce'  //  Only imports debounce
debounce(fn, 300)

Image Optimization

Terminal
// Problem: Unoptimized images
<img src="/large-image.jpg" />  //  No optimization

// Fix: Next.js Image
import Image from 'next/image'

<Image
  src="/large-image.jpg"
  width={800}
  height={600}
  alt="Description"
  placeholder="blur"
  blurDataURL="data:image/..."
/>  //  Automatic optimization
Terminal
// Lazy loading below fold
<Image
  src="/image.jpg"
  loading="lazy"  // Default behavior
  priority={false}  // Don't preload
/>

// Eager loading above fold
<Image
  src="/hero.jpg"
  priority={true}  // Preload this image
/>

Performance Prompts

Ask AI to Optimize

Terminal
"Review this code for performance issues:
[paste code]

Check for:
- Unnecessary re-renders
- N+1 queries
- Missing memoization
- Over-fetching data
- Missing pagination
- Bundle size issues

Suggest specific optimizations."

Generate Optimized Code

Terminal
"Generate a paginated data table component with:
- Server-side pagination
- Efficient re-renders (memoized rows)
- Virtualization for 1000+ rows
- Debounced search
- Optimistic UI updates"

Performance Checklist

Client-Side

Terminal
- [ ] React DevTools shows no unnecessary re-renders
- [ ] Stable references for objects/functions passed as props
- [ ] Large lists use virtualization
- [ ] Heavy components are lazy loaded
- [ ] Images use next/image with proper sizing
- [ ] Bundle analyzer shows no unexpected large imports

Server-Side

Terminal
- [ ] No N+1 query patterns
- [ ] Only needed columns selected
- [ ] Pagination implemented for lists
- [ ] Appropriate caching in place
- [ ] Database indexes for common queries
- [ ] API responses are reasonably sized

Summary

Key optimization areas:

  • Re-renders: Memoize, stable references, virtualization
  • Data fetching: Batched queries, select needed columns
  • Caching: Next.js fetch cache, React cache, unstable_cache
  • Bundle: Dynamic imports, tree shaking, analyze regularly
  • Images: Next.js Image component with proper configuration

Next Steps

Learn security best practices for AI-generated code.

Mark this lesson as complete to track your progress