Technical Guide

Next.js GPT Over-Rendering: Accelerate TTFB

Moves to SSG+ISR, drops TTFB from 1400ms to 250ms.

January 15, 2025 7 min read

The problem

An e-commerce site built with Next.js was taking 1.4-2.8 seconds to load any page. Server costs were $3,200/month for moderate traffic. The React DevTools Profiler showed components re-rendering 15-20 times per page load. Product pages were making 47 API calls for data that rarely changed. Google PageSpeed scored the site at 23/100, and the bounce rate hit 78%.

How AI created this issue

The team had asked GPT to create Next.js pages with data fetching. GPT consistently generated server-side rendering (SSR) for everything:


// GPT's approach: SSR everything, always
export async function getServerSideProps({ params }) {
  // Fetch everything on every request
  const product = await fetch(`${API_URL}/products/${params.id}`).then(r => r.json());
  const reviews = await fetch(`${API_URL}/products/${params.id}/reviews`).then(r => r.json());
  const related = await fetch(`${API_URL}/products/${params.id}/related`).then(r => r.json());
  const categories = await fetch(`${API_URL}/categories`).then(r => r.json());
  const trending = await fetch(`${API_URL}/trending`).then(r => r.json());
  const user = await fetch(`${API_URL}/user`, { headers: cookie }).then(r => r.json());
  const cart = await fetch(`${API_URL}/cart`, { headers: cookie }).then(r => r.json());
  const recommendations = await fetch(`${API_URL}/recommendations`).then(r => r.json());
  
  return {
    props: {
      product,
      reviews,
      related,
      categories,
      trending,
      user,
      cart,
      recommendations
    }
  };
}

// Component with unnecessary re-renders
const ProductPage = ({ product, reviews, related, ...props }) => {
  const [quantity, setQuantity] = useState(1);
  
  // GPT's anti-pattern: recreate functions on every render
  const calculatePrice = () => {
    return product.price * quantity * (1 - product.discount);
  };
  
  const formatPrice = (price) => {
    return new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD'
    }).format(price);
  };
  
  // Causes re-render of entire component tree
  useEffect(() => {
    document.title = `${product.name} - Our Store`;
  });
  
  return (
    // Inline functions cause child re-renders
    <ProductLayout onPriceChange={() => calculatePrice()}>
      {/* ... */}
    </ProductLayout>
  );
};

GPT defaulted to getServerSideProps for all pages, even static content. It didn't suggest ISR, static generation, or proper data fetching strategies. Every page load triggered dozens of API calls and full server renders, making the site painfully slow.

The solution

  1. Static Generation with ISR for product pages: Products update infrequently, perfect for ISR:
    
    // Optimized: Static generation with ISR
    export async function getStaticProps({ params }) {
      const product = await fetch(`${API_URL}/products/${params.id}`).then(r => r.json());
      
      // Only fetch critical data at build time
      return {
        props: { product },
        revalidate: 3600, // Regenerate hourly
      };
    }
    
    export async function getStaticPaths() {
      // Pre-generate top 1000 products
      const products = await fetch(`${API_URL}/products?limit=1000`).then(r => r.json());
      
      return {
        paths: products.map(p => ({ params: { id: p.id } })),
        fallback: 'blocking', // Generate rest on-demand
      };
    }
  2. Client-side fetching for dynamic data: Move user-specific data to the client:
    
    // Optimized component with SWR for dynamic data
    import useSWR from 'swr';
    
    const ProductPage = ({ product }) => {
      // Static data from props
      const [quantity, setQuantity] = useState(1);
      
      // Dynamic data fetched client-side
      const { data: reviews } = useSWR(`/api/products/${product.id}/reviews`);
      const { data: user } = useSWR('/api/user');
      const { data: cart } = useSWR('/api/cart', {
        refreshInterval: 30000 // Refresh cart every 30s
      });
      
      // Memoize expensive calculations
      const finalPrice = useMemo(() => {
        return product.price * quantity * (1 - product.discount);
      }, [product.price, quantity, product.discount]);
      
      // Stable function references
      const handleAddToCart = useCallback(() => {
        addToCart(product.id, quantity);
      }, [product.id, quantity]);
      
      return <ProductLayout product={product} onAddToCart={handleAddToCart} />;
    };
  3. API route optimization: Combined multiple endpoints into efficient queries
  4. Edge caching: Implemented Vercel Edge Config for global data
  5. Component memoization: Prevented unnecessary re-renders with React.memo

The results

  • TTFB improved from 1,400ms to 250ms (82% faster)
  • Server costs dropped from $3,200 to $480/month (85% reduction)
  • PageSpeed score: 23 → 94
  • Bounce rate reduced from 78% to 31%
  • API calls per page: 47 → 3 (94% reduction)
  • Build time increased but deploy preview time dropped 90%

The team learned that AI tools often default to the simplest rendering strategy (SSR) without considering performance implications. They now explicitly specify rendering requirements in prompts and use Next.js's rendering strategies appropriately: SSG for marketing pages, ISR for products, and SSR only for truly dynamic content.

Ready to fix your codebase?

Let us analyze your application and resolve these issues before they impact your users.

Get Diagnostic Assessment →