Skip to main content
Modernization
11 min read
January 28, 2026

The Frontend Performance Optimization Checklist You'll Actually Use

A practical, prioritized guide to making your React app fast

Segev Sinay

Segev Sinay

Frontend Architect

Share:

Performance advice on the internet falls into two categories: obvious tips you already know ("minimize your JavaScript") and exotic micro-optimizations that make zero difference for 99% of apps ("use WebAssembly for your todo list"). Here's something different: a prioritized checklist based on what actually moves the needle, ordered by impact.

I've optimized dashboards from 11-second renders to sub-second. I've cut bundle sizes by 60%. And the pattern is always the same — 3-4 high-impact changes make 90% of the difference.

Priority 1: Don't Ship What Users Don't Need

The fastest code is code that never runs. Before you optimize how your JavaScript executes, optimize how much of it you send.

Code Splitting by Route

If your entire app is a single JavaScript bundle, a user visiting your landing page downloads the code for your dashboard, settings page, and every other route. Fix this first.

// Next.js: Automatic. Each page is its own bundle.

// React + Vite: Dynamic imports
import { lazy, Suspense } from "react";

const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Analyze Your Bundle

You can't optimize what you can't see.

# Next.js
ANALYZE=true npm run build

# Vite
npx vite-bundle-visualizer

Look for:

  • Large dependencies you don't need. I once found a 200KB date library used for a single formatDate call. Replaced with 2 lines of Intl.DateTimeFormat.
  • Duplicate packages. Multiple versions of the same library bundled separately.
  • Unused exports. Libraries that don't tree-shake properly.

The Heavy Library Audit

Check your top 5 heaviest dependencies. For each one, ask: "Can I replace this with a smaller alternative or native API?"

| Heavy | Lightweight Alternative | |-------|------------------------| | moment.js (300KB) | date-fns (tree-shakeable) or Intl API (0KB) | | lodash (70KB) | lodash-es (tree-shakeable) or native methods | | chart.js (200KB) | Recharts (tree-shakeable) | | axios (13KB) | Native fetch (0KB) |

Priority 2: Optimize Images

Images are typically 50-70% of a page's total weight. This is the highest-ROI optimization for most websites.

Use Next.js Image Component

import Image from "next/image";

// Automatic: WebP/AVIF conversion, responsive sizes, lazy loading
<Image
  src="/hero.jpg"
  alt="Dashboard screenshot"
  width={1200}
  height={630}
  priority  // Only for above-the-fold images
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

For Non-Next.js Apps

<!-- Responsive images with modern formats -->
<picture>
  <source srcset="hero.avif" type="image/avif" />
  <source srcset="hero.webp" type="image/webp" />
  <img
    src="hero.jpg"
    alt="Dashboard screenshot"
    loading="lazy"
    decoding="async"
    width="1200"
    height="630"
  />
</picture>

The Image Checklist

  • [ ] All images have explicit width and height (prevents layout shift)
  • [ ] Below-the-fold images use loading="lazy"
  • [ ] Above-the-fold images use priority or fetchpriority="high"
  • [ ] Images are served in WebP or AVIF format
  • [ ] Images are sized appropriately (don't serve 4000px images on mobile)

Priority 3: Fix Rendering Performance

Identify Unnecessary Re-renders

Install React DevTools Profiler. Record an interaction. Look for components that re-render when they shouldn't.

Common culprits:

// BAD: New object created every render, child always re-renders
<ChildComponent style={{ color: "red" }} />

// GOOD: Stable reference
const style = useMemo(() => ({ color: "red" }), []);
<ChildComponent style={style} />
// BAD: New function created every render
<Button onClick={() => handleClick(item.id)} />

// GOOD: Stable callback
const handleItemClick = useCallback((id: string) => {
  handleClick(id);
}, [handleClick]);

Virtualize Long Lists

Any list with 50+ items should be virtualized. Period.

import { useVirtualizer } from "@tanstack/react-virtual";

// Instead of rendering 1,000 items:
// Render only ~20 visible items + 10 buffer items

Debounce Expensive Operations

// Search input: don't fetch on every keystroke
const debouncedSearch = useDebouncedCallback((query: string) => {
  fetchResults(query);
}, 300);

<input onChange={(e) => debouncedSearch(e.target.value)} />

Priority 4: Core Web Vitals

Google measures three metrics. Optimize them in this order:

LCP (Largest Contentful Paint) — Target: < 2.5s

The time until the largest visible element renders. Usually a hero image or heading.

  • Preload the LCP image: <link rel="preload" as="image" href="hero.webp">
  • Use priority on Next.js Image for hero images
  • Eliminate render-blocking CSS (inline critical CSS)
  • Use a CDN for static assets

CLS (Cumulative Layout Shift) — Target: < 0.1

Visual stability. Things shouldn't jump around as the page loads.

  • Always set width and height on images and videos
  • Reserve space for dynamic content (skeleton loaders)
  • Don't inject content above existing content
  • Use font-display: swap for web fonts

INP (Interaction to Next Paint) — Target: < 200ms

How quickly the page responds to user input.

  • Keep the main thread free (no synchronous heavy computation)
  • Use startTransition for non-urgent state updates
  • Break up long tasks with requestIdleCallback
  • Virtualize long lists

The 80/20 Summary

If you only do four things:

  1. Code split by route — don't ship code users don't need
  2. Optimize images — biggest weight savings for least effort
  3. Virtualize long lists — from 10,000 DOM nodes to 200
  4. Memoize expensive computations — stop recalculating what hasn't changed

These four changes will make more difference than every micro-optimization combined. Start here. Measure. Then decide if you need to go deeper.

performance
Core Web Vitals
React
optimization
code splitting
image optimization
rendering

Related Articles

Get started

Ready to Level Up Your Product?

I take on a handful of companies at a time. Reach out to discuss your challenges and see if there's a fit.

Send a Message