Vincent Ombogo
AboutServicesProjectsBlogTestimonialsContact
Background grid pattern

Let's work together

Interested in collaborating? Reach out and let's build something great.

Copyright © 2026 Vincent Ombogo

Back to Blog
Building Performant React Applications: A Practical Guide
ReactPerformanceJavaScriptWeb Development

Building Performant React Applications: A Practical Guide

Learn the essential techniques for building fast, responsive React applications — from code splitting and lazy loading to memoization and state management optimization.

Vincent OmbogoJuly 15, 202610 min read

Why Performance Matters

In today's web landscape, performance isn't just a nice-to-have — it's a critical factor in user retention, conversion rates, and SEO rankings. Studies show that a 1-second delay in page load time can result in a 7% reduction in conversions.

The Core Web Vitals

Google's Core Web Vitals have become the standard for measuring web performance:

  • Largest Contentful Paint (LCP): Should occur within 2.5 seconds
  • First Input Delay (FID): Should be less than 100 milliseconds
  • Cumulative Layout Shift (CLS): Should be less than 0.1

Code Splitting: The Low-Hanging Fruit

One of the easiest ways to improve performance is through code splitting. Instead of loading your entire application at once, you load only what's needed for the current view.

React.lazy and Suspense

hljs tsx
import { lazy, Suspense } from "react";

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

export default function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/analytics" element={<Analytics />} />
      </Routes>
    </Suspense>
  );
}

Route-Based Splitting in Next.js

Next.js makes this even easier with file-system based routing and automatic code splitting:

hljs tsx
// app/dashboard/page.tsx — automatically code-split
export default function DashboardPage() {
  return <Dashboard />;
}

Memoization: Avoiding Unnecessary Re-renders

React re-renders components when their state or props change. But sometimes, components re-render when they don't need to. That's where memoization comes in.

React.memo

hljs tsx
import { memo } from "react";

const ExpensiveComponent = memo(function ExpensiveComponent({
  data,
}: {
  data: ComplexData;
}) {
  // This only re-renders when `data` changes
  return <div>{/* expensive rendering */}</div>;
});

useMemo and useCallback

hljs tsx
import { useMemo, useCallback } from "react";

function SearchResults({ query, items }: Props) {
  // Only recompute when query or items change
  const filtered = useMemo(
    () => items.filter((item) => item.name.includes(query)),
    [query, items]
  );

  // Stable callback reference
  const handleClick = useCallback(
    (id: string) => {
      analytics.track("item_clicked", { id, query });
    },
    [query]
  );

  return (
    <ul>
      {filtered.map((item) => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

Image Optimization

Images are often the largest assets on a page. Optimizing them can dramatically improve load times.

Next.js Image Component

hljs tsx
import Image from "next/image";

export default function Hero() {
  return (
    <Image
      src="/hero.webp"
      alt="Hero image"
      width={1200}
      height={600}
      priority // Load above-the-fold images eagerly
      placeholder="blur" // Show blur-up while loading
      blurDataURL="data:image/webp;base64,..." // Tiny placeholder
    />
  );
}

Responsive Images with Art Direction

hljs tsx
<picture>
  <source media="(min-width: 1024px)" srcSet="/hero-desktop.webp" />
  <source media="(min-width: 640px)" srcSet="/hero-tablet.webp" />
  <img src="/hero-mobile.webp" alt="Hero" loading="lazy" />
</picture>

State Management Best Practices

Poor state management is a common source of performance issues. Here are some guidelines:

Keep State Close to Where It's Used

hljs tsx
// ❌ Bad: State lifted too high
function App() {
  const [search, setSearch] = useState("");
  return (
    <div>
      <Header search={search} onSearchChange={setSearch} />
      <Sidebar />
      <MainContent search={search} />
      <Footer />
    </div>
  );
}

// ✅ Good: State in the component that needs it
function SearchPage() {
  const [search, setSearch] = useState("");
  return (
    <div>
      <SearchBar value={search} onChange={setSearch} />
      <SearchResults query={search} />
    </div>
  );
}

Use Context Wisely

hljs tsx
// Split contexts to prevent unnecessary re-renders
const ThemeContext = createContext<Theme>("light");
const UserContext = createContext<User | null>(null);

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <UserContext.Provider value={user}>
        <Dashboard />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

Measuring Performance

You can't improve what you don't measure. Here are the tools I use:

  1. Lighthouse — Built into Chrome DevTools
  2. Web Vitals Library — Real user monitoring
  3. React DevTools Profiler — Component-level profiling
  4. Bundle Analyzer — Visualize bundle size
hljs bash
# Add bundle analyzer to Next.js
pnpm add @next/bundle-analyzer

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

Real-World Results

After applying these techniques to a client's e-commerce site, we achieved:

MetricBeforeAfterImprovement
LCP4.2s1.8s57% faster
TTI5.1s2.3s55% faster
Bundle Size420KB180KB57% smaller
Lighthouse Score6296+34 points

Key Takeaways

  1. Start with measurements — know your baseline before optimizing
  2. Code split aggressively — load only what's needed
  3. Optimize images — they're usually the biggest bottleneck
  4. Memoize strategically — don't over-optimize, but target expensive computations
  5. Keep state local — avoid lifting state higher than necessary

Performance optimization is an ongoing process, not a one-time task. Make it part of your development workflow, and your users will thank you.


Want to discuss performance optimization for your project? I'm always open to new challenges. Get in touch and let's make your app blazing fast.

Share this article:

Related Articles

Getting Started with Next.js 16: What's New and Why You Should Care
Next.jsReact

Getting Started with Next.js 16: What's New and Why You Should Care

Next.js 16 brings groundbreaking features including the Turbopack stable release, React 19 support, and enhanced server actions. Here's everything you need to know.

Jul 20, 20268 min read
Understanding TypeScript Generics: From Basics to Advanced Patterns
TypeScriptJavaScript

Understanding TypeScript Generics: From Basics to Advanced Patterns

A comprehensive guide to TypeScript generics — covering everything from simple type parameters to advanced patterns like conditional types, mapped types, and template literals.

Jul 10, 202612 min read
Why Every Small and Medium Business Needs a Professional Website in 2026
BusinessSMEs

Why Every Small and Medium Business Needs a Professional Website in 2026

Discover how a modern website helps small and medium businesses attract more customers, build trust, and increase sales

Aug 5, 20264 min read