Chapters

Hide chapters

React Apprentice

First Edition · web · React 8.0.0 · Visual Studio Code

Section I: Rendering Right

Section 1: 7 chapters
Show chapters Hide chapters

17. Performance, Production, and Deployment
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

The Learning Tracker is built and tested. One thing remains: getting it in front of people — fast, and on a real URL.

The previous chapter gave you a safety net of tests. This chapter is about the last mile. First, you’ll make the app measurably slow on purpose, then use the React DevTools Profiler to find the cost and useMemo to remove it — measuring before and after, never guessing. Then you’ll create a production build, preview it locally, and deploy the whole thing to the web on Netlify, with working links and shared state intact.

A word of warning up front: Performance work is easy to get wrong by doing too much. The rule that runs through this chapter is measure first. Optimizations you add without a measurement are just complexity you can’t justify — so you’ll add exactly one, and only because the Profiler proves it earns its place.

What Makes React Re-render

A component re-renders for three reasons: Its state changes, its parent re-renders (passing possibly-new props), or a context it reads changes. That’s the whole list. When any of these happens, React calls the component function again to produce its next output.

Calling the function is what “render” means — and it’s easy to conflate with the browser drawing pixels. They’re separate stages:

A state change flows through React's render, reconcile and commit steps; then the browser paints — separate stages.
A state change flows through React's render, reconcile and commit steps; then the browser paints — separate stages.

React renders (calls your components), reconciles (diffs the new tree against the old), then commits only the differences to the DOM, and finally the browser paints. The key insight for performance: A component can render many times while the screen never changes — if the output matches last time, there’s nothing to paint. But those renders still cost you, because React runs their code every time. Cutting wasted work in the render phase is what today’s optimization is about.

Adding an Expensive Calculation

To profile a slow render, you need a slow render. Give the catalog a small “at a glance” summary — total hours and a breakdown by level — computed by a deliberately heavy function. Create src/catalogSummary.ts:

import type { Course, CourseLevel } from './types/course.ts'

export type CatalogSummary = {
  totalHours: number
  byLevel: Record<CourseLevel, number>
}

export function summarizeCatalog(
  courses: Course[],
): CatalogSummary {
  let totalHours = 0
  const byLevel: Record<CourseLevel, number> = {
    Beginner: 0,
    Intermediate: 0,
    Advanced: 0,
  }

  // Stand-in for a genuinely expensive analysis — imagine
  // scoring thousands of courses. The repeated passes keep the
  // cost visible in the Profiler; real work would be your own
  // heavy computation.
  for (let pass = 0; pass < 150_000; pass++) {
    totalHours = 0
    byLevel.Beginner = 0
    byLevel.Intermediate = 0
    byLevel.Advanced = 0
    for (const course of courses) {
      totalHours += course.durationHours ?? 0
      byLevel[course.level] += 1
    }
  }

  return { totalHours, byLevel }
}
import type { CatalogSummary } from '../catalogSummary.ts'
  onRetry: () => void
  summary: CatalogSummary
  onRetry,
  summary,
          <p className="catalog-summary">
            {summary.totalHours} hours of learning ·{' '}
            {summary.byLevel.Beginner} beginner ·{' '}
            {summary.byLevel.Intermediate} intermediate ·{' '}
            {summary.byLevel.Advanced} advanced
          </p>
import { summarizeCatalog } from './catalogSummary.ts'
  const summary = summarizeCatalog(courses)
              summary={summary}
.catalog-summary {
  margin: 0 0 12px;
  font-size: 0.9rem;
  color: var(--text-muted);
}
The catalog's new at-a-glance summary line, above the search box.
Kqu fameten'm pup iz-i-ynacco bixyibt riso, uhuqe pya viovpk sum.

Measuring With the Profiler

Guessing at performance is how you waste an afternoon optimizing the wrong thing. Measure instead. Open the app with React DevTools installed (you added it last chapter) and switch to the Profiler tab.

The Profiler flamegraph: App's render takes about 22ms on a single keystroke, dwarfing its children.
Kxe Ngezemow xwoyubfotk: Enk'j germaz hevuj ihoiv 60yf ut u pujdxo lijrkzofo, htebxirh oxd wnunkley.

Optimizing What You Measured

You have a real, measured cost. Before reaching for a memoization hook, ask the first optimization question: Is this work even necessary here?

import {
  useEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from 'react'
  const summary = useMemo(() => {
    const catalog =
      request.status === 'success' ? request.courses : []
    return summarizeCatalog([
      ...catalog,
      ...personalCourses,
    ])
  }, [request, personalCourses])
The same keystroke after useMemo: App renders in about 1ms, the expensive summary skipped.
Bnu yasu yovghhoba apwuq evoWumo: Ibm vifqetg oc awuif 2hn, kvu owqozhawe wuxrund ryabwog.

Memoizing Components and Callbacks

useMemo caches a value. React has two neighbors for caching other things, worth knowing even though this project doesn’t need them.

const CourseCard = memo(function CourseCard(props) {
  // …only re-renders when its props actually change
})

A Final Pass Before Shipping

Performance done, give the finished app a last look. Run through this quickly:

npm test

Building for Production

Everything so far has run through Vite’s dev server, which prioritizes fast feedback over a lean result. A production build does the opposite: It type-checks, bundles and minifies your code into small static files ready to serve. Run it:

npm run build
The production build: TypeScript checks pass, then Vite writes hashed files to the dist folder.
Zba vsugaknuad tiubt: XkkuHmzang qkutrc hemb, fjaq Riza dmubiw kilpuk mifif le vgo vezq fumxuf.

Configuring the SPA Fallback

One detail decides whether routing survives deployment. The Learning Tracker is a single-page app: The server only ever has one real HTML file, and React Router builds every other “page” in the browser. Visit / and the host serves index.html; the router takes it from there.

/*  /index.html  200
npm run build

Previewing the Production Build

Before deploying, run the built files locally to catch anything the dev server hid. Vite has a command for exactly this:

npm run preview
The production build running locally through vite preview — the same bundled app you'll upload.
Wqa yjopocgeer zuals febzany pusaztd dvluavk kabi gtunuuf — yfa jewo civrhic ozt kai'tx ofriet.

Deploying to Netlify

Time to put it online. Netlify hosts static sites for free and understands the dist-plus-_redirects setup you just built. The fastest path needs no account setup beyond signing in:

Verifying the Live App

A deploy isn’t done until you’ve checked it like a user. Open your Netlify URL and walk the list:

A deployment checklist: verify the build, the fallback, and the key journeys on the live URL.
A yatlorqomt hlibjdadh: moyihg dmu siidf, jba rihfjecf, ehx jve fuv yuiwbant iy mxi fope OGN.

Challenge: Add a Plan Total

The catalog summarizes itself; give My Learning the same treatment. Add a line showing the total hours of the courses in the plan — for example, “12 hours in your plan” — above the list.

Key Points

  • A component re-renders when its state, its parent, or a context it reads changes — and rendering (React calling your function) is separate from the browser painting pixels.
  • Measure before optimizing. The React DevTools Profiler shows which components rendered and how long each took; optimize what it points at, not what you guess.
  • Keep derived data derived — computing it in an effect and storing it in state adds renders and bugs. If it’s expensive, memoize instead.
  • useMemo caches a computed value and recomputes only when a dependency changes. Depend on the underlying state, not on arrays rebuilt each render.
  • memo and useCallback cache components and functions; because functions get a new identity every render, a memoized child that receives callbacks needs them kept stable or memo won’t help. Reach for them only when a measurement demands it.
  • npm run build type-checks and writes a static dist folder; npm run preview serves it locally as a close stand-in for a host — good for catching build problems, though the real host adds its own routing and caching.
  • A single-page app needs a fallback rule (_redirects) so direct links to routed pages serve index.html instead of a 404.
  • Verify the live deploy like a user — especially a direct link to a deep route.

Where to Go From Here?

You’ve taken the Learning Tracker from an empty folder to a tested, profiled, deployed application on a real URL — the full arc of a React project. That’s no small thing: Data flows through props, state drives the UI, effects reach outside React, custom hooks package logic, context shares preferences, routes organize pages, tests guard behavior, and a measured optimization keeps it fast.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now