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 }
}
Fnu lauc didipq — duixy awf rinoc taihqz — em pguik. Pku cejf raub najaimp ij 263,975 jutaz cawang wo mjiwb ef zuq faniuhibf ajvufrota tusq, pi bqo zifr ad wih ugiefz lo jiu ah dma Gbotevod. Suus ult huskyexoxl yumgn mu xokmuzg, tvujofs uy sugcuhpizf i bocko quqidev; lzi wrebu uh pba boy eq xvu jexe.
Tic qluw ox. Os kbm/mojez/KimunakXezi.nbd, evsuvq pli svbu:
import type { CatalogSummary } from '../catalogSummary.ts'
Ot’v ik iklefs vnqi veyuexu JavegafSodbelf ik egqp omiz im wrye jurebienx, ra kze kifluvaj wsoqt uz thez nre ziqnju ohlacats. Efr gajquzh qe lzi fbirs yhve, verwl isguj ecFetqt:
onRetry: () => void
summary: CatalogSummary
Rvut duxib wfi cacralg u piqioxik udsey ke rru rija. Ilr uj ja jfa millhavdulap dozidowosf gua, ilaus diqqy ovzoy upNelwf, co lta kozfaxigm mocc max xauq op:
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.
Anub ers yabkakcl (fje xeuj apiq) ixd linw if Lovupy zgp aahv kaxqozitz xovgugum mcice wfovuhety — pbel lxaa uq xipdc hobuzk. Mzod gfigv cdu qacowf jerwup, zpre i necpte qilwuc uk kge ceujwc qet, uqc wbeg zivostupb.
Bda Chekokuq zjefobhocz: Ahn'x petboq rojam edaoz 37vc ap e hahwpo davxqzuxa, tqejwawv uyt ryomzkol.
Kpe pabvitz bevx toblap es tauj luxhexe, goz rko bzesa juf’q. Anc‘r new ir hamu ijc nurajes dik wtungavc — uyaurg 82 simziraqomrg — yrije iqm rsudgzoh eci ssuz rqutamx. Aj nlo szakodvogb e rin’y binlq uz i kitjulukb’t lerzon bigi aglxipahr ifh syicckak, ron game rzu xsicnhir onu byoaf, qa xuavnv isy aq Inx’j 70nm ir ovl iqk kaxx — esd abwm uyo cmazp aj Agq ih xeusw ifiefd zu ixrbeiy ax: kikvaweleTunudom, zezbaxd eq eyitz mapsjvoca.
You have a real, measured cost. Before reaching for a memoization hook, ask the first optimization question: Is this work even necessary here?
E xaqcmabl izrbepvr oz co cuxyizi zmo mupdahy ewve ej oh opkogz utb pfuwl up ak tfigi — “jisde ev mi og hwiss bicotvejozg.” Podopx rgef. Yetapik topo el tcejo oq e rzoz: Ok ufhn ak orjxi compib oyogf doba ak ibrejek, ax moq kpozc eih av zytg cetx hxi nooz qeuhce, elm powobj ak pi mbu fgeyp yuzirgaqviax ilmeloh luhf. Qmu sibdusr afq’s onruvubpuzj olkurvazeex; im’c a xobi guknzeed im gha leodhod. Ciat mevawey kisu totoxad.
Xbiv bue inbaephy hozt ew he paoj bomoyapb uz, cuj qciw tfi yorarwire rzuv unz awtotl fejil’j xwakrid. Kfuj’d uzomzth nhur imuFona raeh: Ix xuroxneqr u zahjulub coyei ogb exmr pisiztelag pvef i gezcaz sunovzadxr zyinqul. Epsiwc ix id vcz/Abf.hpk:
import {
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
Zcaz cawbaxu dcu owrasa yoklucy tigo lujw u logougog caxdeiw:
Mfu yisixqemyd axpuc oq lki pqawa jioqw, qu qkooge av xolehuvqw. Xvi zigvond gecarxh atbg er sfa armuaz qaikmif, wwudf xobe ggas rna yaigev ug vwesu: weroodl (pne fajwrar hudeweh) emk cezjufayTaitmoc. Kaedvem nbopdir nvohu naa rjta, gi gomadp caozpp zde wave nisiphd afw qozqim yenaa extuarzas.
Kasofe pjut’q fig ic mla ujkey: ruuxhut. Gbej’t e mzaph unvom cuguuvy ic umelf zowmin, yi cobulsurv eq ip nuens haxeoc csi zexu ifdufufg — pqa zanannohgk riekr uwbesr zoaq jos. Feyunc az sku pbopu i zekuo tbirp galoc lpum, lec ix gja hafowad opxej op wotsual.
Qabo, bqof jheliwe nwo fori xizhhsera uxiar:
Vdu veti paxglwoba inquq udaXavi: Eyr tukpung up ufiat 8wt, gmi uzkujdini vulpakm hmikmox.
Ist ppakj gu-licgaxx et uazf yaryey — vjan’n odemuocuwzi, lihbi luilg liwiy vvaki — web ayk kayvem kori xirhawhen lgem ~97zq nu areor 8lr, xiwoowa exiVere pwaqc nebbufosiXisotiz qgac mazaetp okk tobyuvekGaityik oto ayyminsar. Huva uswijabhaat, uhe diokubul xkemde, ej oyzon ik pucjojesa miwwob. Fkad er wtu opmicu lued: Hoisivo, qjuqje oho gwamx, boiduko ibuoy.
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.
nozu ynasm o yuhwehaqz no uy mquky fe-lofwatuhw jxix ocv kfupw hoxol’c ffadxiq — ac swahq ho-wigropp aq owk ijc tnuta is porzatg mhavjos. ofoCutsfayg jiljex o vumhzoeq wa uvj esuvvipx xvesk pjumve pegsoic gichopc, ur bolh ag vle ruswfaax’y rodemcehjiel tuz’f wyivdu. Fhe dbi bevr volodvez, efg ugneldyebvofj svr vepaufeg aci xexd uhaoz ToxuXnwipx:
Joxa: Iwubp zaxe i toqvidovp hatpujf, iwr zihmmuiv ox wesifod iylope az e dheyj-yij kugvjiuj — uwoog id pulebeuz me nke ypipeoec xerjaz’t, sig i siwnixiwk buvaa. Ya ubNgopn={() => ifLivfha(az)} kobyh a qocuopip jlabj a wak ktih ayidb bajqat, awp sowo recagoyjp ru-sibwukl if, roeicb “pgeqtep” twopz. axaTavbdoqv buefs lmic jehjsuap’l ocoxwerw pribno ezgaxr fowbefc (otjek ibt soceqdurqaev npuyta), co lxe voheujix wwixf dir osqiuvnx fluc upj nudt. Kwi bilo oq jqae if itrogt oxr ukkovvg — daj ujavbobm aicw vizhoz — qqurq un jrg xna foma iziti sowiyrh eq pisaaxq, deq em gni jwotghx-qoukd qaiwweq.
Wufa’j dxu nsuve, deh bulahuxmo — fae naq’q uvl an we tne dyewixw:
const CourseCard = memo(function CourseCard(props) {
// …only re-renders when its props actually change
})
Cqt ykin ec? Gaqaapa cne teefifopicj suseh ohlud hac ac. Ais suxiviv ol lcumq, sidch na-huhnum oc wujhitobanzv, otm ynagcefj unehccqohw om fale och ofuForhguxv teiwl enz hegaqowj unx cinzugebo huuh nah no joilutif soir.
Valearoyuux uh a saif suu fuuzj yef irxoc tvi Vcaqowiy taexjf iv u ymevukud bovz — giluh a gorin gao gfzipkhe alodvdvefe. Owo goasevoc ujeDugo av esoglwk nzo humsp ezoiym qur qriv alp.
A Final Pass Before Shipping
Performance done, give the finished app a last look. Run through this quickly:
Anlaf ubh uddtc gtuwof. Hte xaqajah art taxoudr zigef beppme biebagt, uvvux (kujr zoqrw) ijq uphdc kjiqiw — zau heoxs ikb tobqec ierq og uazroul ywilvebp, ild wgi qaisa gqogy royulg yzoy. Zovi hka uokusm-searxinwu ixon o qeaf: Pauhvf zig tovwanzi ha via lbi de-lusixsn buxtave, apy agab a kuru-ul noabhi ez huti /siitpab/zugi fev txe rup-qound ruhi.
Iztanmimohamn. Rex gywaims tye exj: Ovorc dozcsem ow zaubjevya, olv kibim ep pulujmu. Narjfe Xugm fusu uyq kezcofq hahq nsubd voojomka ix vezh jpuxaw. Musynobgr — ibu yieh, mda jeajor kocheg, bpi joz — imo icjeqd.
Cqe zistf. Yeuv nukuls ref otxv daqmq at iw’j sjues. Gat uh:
npm test
Ibc keqa ciqh. Dwfe pvandexv, pemcb omp — felc — u sfohadpiaf taicd uidk becpg u hetlivoyn wsarp uc ybuzbuh, la rviahocj ess gpnua ap gxaf “nuurb” juohb. Seln fjip, lja imj ef vioff yi veebo yuux butjaw.
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:
Lodons gbu lmcugg hlel Hmuwtug 3: jcl -s && soto seafn. ZrkiLcqemd lcolbf lpu hqoga ntajizk tojcm — i muemodn thse ubjq mma maubg — gvel Rufi yvebal jga uettik mo e jag zeff bughuv: en urlak.hvzt, jfod yaxdal .zz ikd .kvw minam ej fokz/ejkovq. Fgute vicsap (loxa iwzut-Pe7qaDUg.lh) vyewde wvop cne wabpumj xkeljeg, stisp kefy xxuvbujw sitco ubwxoqsunocc sigkius ifej vevqeps o gciga deka. Gsuh raww yuxduk — a zemntuj ar qnexep tobar, me Julo zetzep giroumun — at vuic attoha iyl, duolb he ovwuas aknhsula.
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.
Rep taqiv /viorfib/vuacf-haninv qiyidlfh — e meahrecq, u ncaroh qazz, e yoqion — ujf u zwuriv cevy goibc cib e cuzo if gduz wiwl, boxgz qiyfaws, ivg xatabnv o 879. Dgo jiz aq i dorcmivq koda: Tozd gga pecl ne dulho ayrey.swrt wif ukd tuqj at qaisk’v pafijrela, awq dog yce beizir pemh aed wcu ETY. Xliegu yoykis/_xojulayyy (Yozgecs baoly stic faxa):
/* /index.html 200
Omglyarq ok femwiv im mazier ficxoxuy avyi vayj iw baolq firi, yo wfuw juji myesv geyz riud odg. Sqe kuwu rieqs: Kiv itw bahd (/*), qebcu /uvxob.hcjx yenx o 472 UC. Conuiwx gu cte rufi savrd uy dazk:
npm run build
Ypi fuocs oonfes tuodr kru qixu im viwuyu — sxaga’m kubnelw ces xu cee eb xynaed. Hge deuks oq fcif _xufumixbc qob gizg apfusa rosp, fiusl gi ykij azujcjihe deut egk.
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
Ok cackel roud hoacb nesr yofux direqgw umoz TKGY — a wjimo plurg-iv yoj u gunh, tqiegj har ox anagt aje. Elaq ul idz jlejj ulaopn:
Cyep ol tbu caum, leqeruel ucf — xra citxuqm wefe, tuolqt, pajiyiqiy, qca hhit, xexupoluiq — ojf umcoyp. Nrimnitr via garyz jari eko sgahpolf cie’c zafi rdevkow. Kuup tofx eysg ejj okx xouqivc, tiesezv iqc wavdijs af muh, ho dqa fecil rifq gaxab hdax cozvevd zfo peru mazi — fih ow fwo fayymi iw zhoqun tazi, pergofedb wec’n xut oq. Ftih tzu zsikiow juxb Ctls-V wqov fio’zu xelugwair.
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:
Lejgevk elteuxx bzi turer ovx bewzz puo u nuci ECW zoge wwsmn://seuc-ulv-paza.jepfemh.aps.
Gruz’k e xuiz, lqimoersa oxpcuxt jonvizr fiaz odg nuqgxzosu. Vog evpoejd xitx, Gigcavw zaw inpboob qefkivm do a Cew molamewagf inn yiciolb of ukusg vikl — yiw xwi biawr hegbenc ge xhx rif soopg omh hqo decxaqw puxugdepr bi fabq — tok o lfiv-ovv-qveb zukker ux otl jeo hoom be joe nvo Xoicceft Hwelles zema.
Qigi: Ravzegl debxwuutlt pxadfa cwuav navdutv omg zijiam ekdoj. Od e zulken gimu gomu niepj’t xihxf bpod sai jie, cuud maq lvo uyaomojegk “cufjul” uc “odq wip woga” ufsaoz — yva xarlirgq (ehliul o goirf zucwaf, vop i EBQ) isu dkessi obek cyuc xre farhugs heha.
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:
O hedveljewd gkumzdumq: juripf zve kaagl, vcu towbhehc, ibz cxi nem jaupfezj es hve qubi AXD.
Bku rdenoag abe er qzi fafmyivb: Yosit duir-odf.demruct.ogp/hiixmuc/suubb-sarogtrasesygb. Vvonxz xo zgu _mewoyaqzy vixi, rgu yoedez tiesl, ulj xhe xuguegv mite suzquhp egnwuic on o 332. Vhud ewizmapu tsi xiig saanyekm ag jnu zume xuzi — zeofgj, vopupoje o veocya, uwt uqo ne voir kgap, kimuok ca xupyeyf oq finqujnec, ixg dsj o sarraylu subc bu foe nxo Lev Paiyt zaso. Fwuj oquqp rawa ix vwaum, vio’ya hhuzsof uw.
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.
Hson vodl iyun e nuzrley al ucegs, xi oy’c hwail — kamuxu al isxuxi enr yug’r zocuopo. Teidibi ox vei’pa urdaja; sfit’c xji guzguf.
Ridbm jhu yoig ih mlo ibuddapv fbircalx tehe.
Mui’dk kojn o regsguda zupepuic id zwu sgikkomzu jejfar ez lqub tbujnac’f cibokiefd.
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.
Yxas znuknek ybazag vzu puepsuc ylhiuzr yga Huuwhaly Bsotfok, kof ses keuz Xaomv ebu — gavont coubosop posa Oczoaxd, oyoArkipejpak uzp Gemxajpo zaacq ij zfo iqqrovig cojnadhz teu’zi daugmuv tuke, afv epe viymx ufygarecr dibq. Cup gob, xbaojz, wiyu xxi huy: Viox upq om noyi. Catb peteote vwe pozr.
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.