All Posts
March 10, 20268 min read

7 Ways to Make Your React/Next.js App Optimized

ReactNext.jsPerformanceOptimization

Why Performance Matters

A slow website doesn't just frustrate users ~ it costs you traffic, conversions, and credibility. Google uses Core Web Vitals as a ranking signal, and users expect pages to load in under 2 seconds. Here are 7 proven strategies to supercharge your React/Next.js application.


1. Use Server Components by Default

Next.js 13+ introduced React Server Components (RSC) as the default in the App Router. Server Components render on the server and send zero JavaScript to the client.

Why it matters: Less JavaScript = faster page loads. Only add "use client" when you need interactivity (event handlers, hooks, browser APIs).
// ✅ Server Component (default — no directive needed)
export default function BlogList({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// ❌ Only use "use client" when you actually need client interactivity "use client"; export function LikeButton() { const [liked, setLiked] = useState(false); return <button onClick={() => setLiked(!liked)}>♥</button>; }

Rule of thumb: Push "use client" as far down the component tree as possible.

2. Optimize Images with next/image

The next/image component automatically handles lazy loading, responsive sizing, and modern format conversion (WebP/AVIF).

import Image from "next/image";

export function HeroImage() { return ( <Image src="/hero.jpg" alt="Hero banner" width={1200} height={600} priority // Only for above-the-fold images placeholder="blur" blurDataURL="/hero-blur.jpg" /> ); }

Key tips:
  • Use priority only for LCP (Largest Contentful Paint) images
  • Always specify width and height to prevent layout shift
  • Use placeholder="blur" for a smooth loading experience
  • Serve images in WebP or AVIF format for smaller file sizes

3. Implement Code Splitting & Dynamic Imports

Don't load everything upfront. Use next/dynamic to split heavy components out of your main bundle.

import dynamic from "next/dynamic";

// Heavy chart library only loads when the component mounts const AnalyticsChart = dynamic( () => import("@/components/AnalyticsChart"), { loading: () => <div className="h-64 animate-pulse bg-muted rounded" />, ssr: false, // Skip server rendering if it's a client-only component } );

When to use dynamic imports:
  • Heavy visualization libraries (D3, Chart.js, Recharts)
  • Modals and dialogs that aren't immediately visible
  • Rich text editors
  • Any component that uses browser-only APIs

4. Lazy Load Below-the-Fold Sections

Sections that are not visible on initial page load should use Intersection Observer to defer rendering.

"use client";
import { useRef } from "react";
import { motion, useInView } from "framer-motion";

export function LazySection({ children }) { const ref = useRef(null); const isInView = useInView(ref, { once: true, margin: "-100px" });

return ( <div ref={ref}> {isInView ? ( <motion.div initial={{ opacity: 0, y: 40 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6 }} > {children} </motion.div> ) : ( <div className="min-h-[400px]" /> // Placeholder height )} </div> ); }

This keeps your initial paint fast and only loads content as the user scrolls to it.


5. Minimize Bundle Size — Avoid Heavy Libraries

Every kilobyte matters. Before adding a library, ask: Can I do this with native browser APIs or a lighter alternative?

Heavy LibraryLighter Alternative
Moment.js (300KB)date-fns or dayjs (2-7KB)
Lodash (72KB)Native array methods
Animate.cssCSS @keyframes
Axios (13KB)Native fetch()
jQueryquerySelector + fetch
Pro tip: Use bundlephobia.com to check the size of any npm package before installing it.
# Check what's bloating your bundle
npx @next/bundle-analyzer

6. Use React.memo, useMemo, and useCallback Wisely

Unnecessary re-renders are a silent performance killer. Use memoization strategically:

import { memo, useMemo, useCallback } from "react";

// Memoize expensive child components const ProductCard = memo(function ProductCard({ product }) { return <div>{product.name} — ${product.price}</div>; });

export function ProductList({ products, onSelect }) { // Memoize filtered results const expensiveProducts = useMemo( () => products.filter(p => p.price > 100), [products] );

// Stable callback reference const handleSelect = useCallback( (id) => onSelect(id), [onSelect] );

return expensiveProducts.map(p => ( <ProductCard key={p.id} product={p} onSelect={handleSelect} /> )); }

When NOT to memoize:
  • Simple, lightweight components
  • Values that change on every render anyway
  • Premature optimization without profiling first
Use React DevTools Profiler to identify actual bottlenecks.

7. Enable Static Generation (SSG) Where Possible

If a page's content doesn't change on every request, pre-render it at build time:

// app/blog/[slug]/page.tsx — Server Component

// Pre-generate all blog post pages at build time export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map(post => ({ slug: post.slug })); }

export default async function BlogPost({ params }) { const post = await getPostBySlug(params.slug);

return ( <article> <h1>{post.title}</h1> <div dangerouslySetInnerHTML={{ __html: post.content }} /> </article> ); }

Bonus strategies:
  • Use revalidate for ISR (Incremental Static Regeneration) when data updates periodically
  • Pre-fetch links with for instant navigation
  • Use loading.tsx for streaming UI while data loads

Bonus: Quick Performance Checklist

  • Lighthouse score > 95 on all metrics
  • No layout shift (CLS < 0.1)
  • First Contentful Paint < 1.8s
  • Use next/font for zero-flash font loading
  • Compress assets with gzip/brotli
  • Minimize third-party scripts
  • Use CDN for static assets

Wrapping Up

Performance is a journey, not a one-time task. Continuously measure with Lighthouse, Web Vitals, and real user monitoring. The strategies above have helped me achieve consistent 95+ Lighthouse scores on production Next.js applications — and they can do the same for yours.

Happy optimizing! 🚀