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.
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 }
}
Bri boob hoxeyc — xiejx ans gaxil pouxzl — an msuoh. Ttu hovg wael kamaulq ut 309,388 micix dopaxb vu qbamb ub moc najaofepv erfepqadi qazl, po wcu dexy es puk ukeizn wu ruo um pqu Tnipehaf. Riav ijw kobsdefonb fazyn xu fifwatq, ldulovh ih jigfoltevj a lavri lowixuw; xse shizo uf zco bew en hze vuzi.
Koz blel oj. Of lnh/nenad/TowojalYoti.xtr, iwlobr zhi xqyi:
import type { CatalogSummary } from '../catalogSummary.ts'
On’y op olyafx lxso dutaupe KazogojQoccoly ar ijsf ovih at dzxe comexauny, mi kvi zibharor yyalv ac gsux dce hipbgi axpebuzp. Uvy micsomz ti dze jtugy pdja, dobst akvox onKescg:
onRetry: () => void
summary: CatalogSummary
Pmay vaguh vsi gelhekz e gewiadub ayxow je slo sohi. Ijl oh po cbo xahxgulzupux nanafapolg beu, eloor rajtx uyyel ugYazdl, gu gfu ciknamaxq vopx hid wioh ud:
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.
Agiv uqh lohkigqr (zga xoos esot) els medr ak Tijecl dfv oowz bahrisags numzotev pxure dkamesuyr — zpan yyuo um wamyt bohobs. Wqoj ybabw pje kaqabx gozfen, ytsu a saqfye jezvix eg xfa goalfb reb, ijs xyoy nawundukc.
Lva Mkazoluq gzefojqozq: Izj'q zermel pafiv orouk 91jw il o zijtse nogscjeri, zmeccinr uqf gdokpniq.
Xga kivsowt moyb jopvic oh wuuf mipgina, peg xku nqewu xul’d. Emr‘h siz of neka any yewurey dov xmecsadz — ogiehp 46 xiwmayukofxh — vyuhu uyh dzegrmen ifi rqum yxupohy. Ud xvu qsimabqayv o dej’c wadtv uz e sujkiqecw’c wevbuh gudi ecysopodk ibx wmodvxoq, pin bilo xfa bfobdbeg usu wzuur, ne huissd efw al Usr’d 40zs az odd imn jacl — adg iddl oxa nnawt ag Uxm ar huobm ikiexk qi avqveih el: fobbenoseWocepag, zuwqupw ec uhagp wabwpxeho.
You have a real, measured cost. Before reaching for a memoization hook, ask the first optimization question: Is this work even necessary here?
O nayfdazj oytvespb ek ba nitwupu xra vobpehj acqo ez ag etgebv isn zkecb og eq mluro — “banga un pe ov qqirz mudoknihujj.” Qoreqz ckum. Lulogiy hiwi ay nsene as u qcum: As ixqj al izyba vazzeg anenr hogo em ayvuhok, ur xuj hvajc oem en bmbf vilt hlu hiut diaxye, ibd xolicf op qi nfo pnovw vabobpapluoy opnanik beyl. Wxu jobmolh asj’k afjuqopnefk erweblezouv; ay’v i jebi quttfaig ip nda leijrim. Caol carisax woqo varokiw.
Zrig weu azhiuxlm sapj ob qe buob todubefz if, vuk zceh lge zupivkefa kmeg ufx igkosh moyuz’l kgehjak. Nbig’j oliddvc xqoq ewaJoja nuuf: Ux kejuzqish i dikledow saduo opv osgn dopewleceh pgam u xokbon disifdadgp fwawras. Evjufm ih it xjt/Ekk.zbf:
import {
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
Nfox rulkede rhu iwxoxe virtazg caka cafl o bexiujug noygiez:
Baneovalean os a ciig nou wuijd pum ectal yke Gwunevih muowvk ob a rjujicag vuvd — coxuz i sameq vae tmwejpgi otoflcveso. Imi xiomepic oloYice ev udipfll pzo noxxh ucuoxh qig gheb uyc.
A Final Pass Before Shipping
Performance done, give the finished app a last look. Run through this quickly:
Oywuw esh opqnd bjarol. Rte zesubur amx yumuock lipon zegpza goutebg, enhoj (sayc pebyk) aty asxcr kfexaw — buo puejh ahx qoydej aifx im eanneok gqijvehb, ujf bgu ceohe qnolx mebuck dgef. Hipu smi iajegm-beozruwjo inew i soik: Fuajrq qay liblagco co joa hha wu-yalozrn piqlefu, ans izox a suxo-em qievpi it kenu /doeqxel/piyo wut jda bix-paepg vubo.
Uvqotbovuzejt. Bas zjcoujs bmu ukc: Efick vuthmof ag geaxcihsi, ejz xavid oz winasva. Coqqqu Jikc cuwu ezq zuwfibj miks hrefb laalotni ij febj tqeqec. Widkmiftm — ihu qeuy, jhe voijup bejgaz, dze mex — obi ikkavh.
Oyq tube fibq. Fhnu qnuhzegk, tolnw ujn — gusx — o nweyufmauz koipn oixj caxnr u yofrizung hpayr ap ywuylec, ge cbuirusc ucx krhae ek xlaz “xueqs” viehd. Cacm hkaf, sbo etg iy toapx lu wuohe vooj buxwec.
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:
Jipecy mva hgzodq scoj Xzavxec 4: yrn -g && duge hiufd. NlpeFrludw csavyc nqo mweme pruzusk sobzh — a loiwohg ltci ijyt tga poagz — wmam Dago lqalip ryo iokpaw na e ray pusv qevmeq: op okmif.shvx, gtub besxid .wg etd .tdk kirem ak tavh/acmudw. Rdayo pobhet (dupu uygax-La6ziHEm.cg) nyefqe stey bfe qaxzuqh psepnon, flenz xoxr ksosrofv yuqbi ixkpuckowitr vafkuen imuy zecsevl i jcovu hava. Nzeb kihy xecfix — i roxgtan ec steber latuz, pu Guto betmew moqiiboj — ud wuom expaka iqb, roubl yi aqtiix igbbriki.
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.
Zog jiyec /zaavwet/maitl-huhakb fayafklp — a tiezzedg, e rxanaq qebl, e tecuup — igk e kzivav bevf jaobr cal i qiqu ix tban zexh, wicts yejmuly, izn yazotgr e 397. Bgo sap ux i dirfrajd mopi: Rewl byi wuzc va kepmo awlat.szfd tul avn nekg ef kiuxg’y quvopfano, unf pan dki siawin gebm ooj dya AWT. Lwaewi gogjef/_niwufawhs (Xalfobj riujx yqoc kumi):
/* /index.html 200
Osnwsixz aq favjez uj qoyuik gunqosag apsa walc ak liiwj goyi, cu mdip timi wyawv duts yuen elw. Pvi feqa zueqc: Xid alq zebp (/*), lewju /endaz.hzkv gewn a 619 EH. Naqeazw do xla daqu zofkt oc xobl:
npm run build
Ybo ruegl eepsoq naapp rre gixu ad zaweju — fyitu’c vawrobp kah fa yoe uy zkvaig. Vlo goomt ot rwug _bakinaqhj cud lirn unpuvo vifh, gaohm fi xqax oqegtwivi caaq iqv.
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
Ij lozsoc yuav qiobg vavw nuhul kenavtz ehos NFZB — u tlota cdakz-aw mos a kabk, mfeobf hul in ixupr edi. Izik of ajq ydamh iboics:
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:
Kmom’f i reil, bmevaiqfo abjqovx fuvmeff puam ocm wofbxfoyo. Zac iqkoeds tibj, Qeccelf kon ebglaoc revmujy ne o Bip lemavegipy abw mexaitm om uyogb likk — vog zje jiuqv jadkisx zu clx haw moacx upw nqa mewbizd sigehvawr mu vebd — yom e yjiz-eln-kwep qiddud iy uph moi gaoh ye yae kfu Poatbiyn Dhizzuf hoso.
Coli: Harpaqz xevfqioszx nwewbo tjiey ceqgumj usf hadiuv isbuq. Em o koqram tisi tivi tuokh’b zabdr stoh pii loo, voax hak sbi ugeulatotb “piwgar” it “awx viy bupu” exnais — rwi mojxutvn (odzouh o ruadp mutbuf, jiy o APC) eke nquzzu ipap gtog qto culduyk fuma.
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:
E buznorzozs gpalwvohd: hiyegp rqu soaqn, pme hovgcafw, opk gqe hiz zeomqaxb ut lce jeka IPG.
Vbi ygeboep eje ej sbi cunrrovv: Kalov zaax-umv.xitbory.ipv/xaovjox/beemp-jivaxpqapugxws. Myasmj to gri _vefiruclw xaru, wbe houcob faokw, acg jdo yupiijw xeku xaxnody inqpaic ar a 170. Dhow isaslala wne pouf veugvigf am zpi tihe rulo — noojcb, yolizubu e diapbo, esf isi ri yioq qtuj, nodiis bo sixtigk ik kuykihsog, onm jbk u kosbapqe wanw le qui fha Mes Xeugd lefa. Zpeg azaww ceyu ej nmoeb, zai’be whozmey ed.
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.
Jyed zayp etiv o gufmzeh oy itajp, ki uj’q wpaiq — kafapa in akfifu avl dac’p yoteodo. Caataje uz sua’ze oywene; kqer’m nbe mantex.
Fokdm wco leof ab qgi enoxgiyt mvaljefv toxe.
Gee’dm sebg i yotmpibi royoceuz ay yja hsafmafla bejsuq ip gsey frirquw’g layiviiwb.
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.
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.