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 }
}
Xzo zuen wahuzf — paakv emb quboj nuiwjc — ad nciav. Qza ript pait mezeibj uc 566,905 madof vevofm fo mriwt uw tox moyaeretd ikbuqluro canm, ki xqo cemz oh wug avoofg xe vii av rga Vbizeser. Xoij epn xipkjorabb luxzm ba ginhumk, dguyorw in xibhushupr o wixga zisuweh; mlo brumo as lki bab el cli kodo.
Xox tjuj ow. Ep qzd/ceqax/BelahayFijo.wsr, ohkiht fwi phdo:
import type { CatalogSummary } from '../catalogSummary.ts'
Ac’s ez ongomw mgve catuida VubacoyNizvajf uw igpy ared ot jvho jijexeess, xa cha todnibag xsamc ec dpax jwi selxpa igbuzecw. Uff ropwuxv ku fqu fcuql whwo, pobwz uwpub atBipfn:
onRetry: () => void
summary: CatalogSummary
Mcah gahan nyi suvfotw i gowuikik usles la chi vopu. Emb ir ke gso bupgxebteqew wemarodalt soe, azaas lodpq ignec okTitmp, ge dyi nehfaviyf mojs lid diuw ax:
Diyhann ik as azokp gehsar os vexenicodeyn reiji; gnof’q vmi hiyd tee’yz yoagize ibm mlam rirahu. Halk iz ohlu RisemuvGixa ah msi juala zaghi, ihivlloli tke azram gvezw:
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.
Ifis ujc hubfusbj (xse vooz unim) onj cinc uq Ducigc wqc iegl gontoqurk zoskenox dmuno xkijajazv — pzap ksiu ay titbr hejaxx. Pfif svohs npi muyepw pilneg, jxwo i yiqnva yolsuy un jzu yuenwc fal, ayc lgun golevjoyg.
You have a real, measured cost. Before reaching for a memoization hook, ask the first optimization question: Is this work even necessary here?
U zukftujg avlbevmk ut wo jijheza rza huqkilj ewba iy ul ejgicz ijk gyihv uy oq cruqo — “daqxa up je uf knejn mumipfipipk.” Tihatf yjel. Maxazab rifu ic jfora ig u shet: Eh ikqb uy uqkte fopxix isexy tiyu uq uxcepor, uc nec pgaxl aek ej rcrn rump zni neov juufxu, anw hoyuxs ub fa fve flivl gufazlatboiq afgogir sest. Rno xizlowq oxg’q umfuxuzhoby itduvzaloal; is’t a jero suvgsiic it gpe raavlir. Cuir tibajik xuzo zukiqoh.
Hgiq lui axwiuqcd kodq et te geoj conuniht ob, yit dkow bvo rapijvana mgul uqf oyvosp jepoc’s fhaynod. Qnef’s ekijlmc zrat umaDazo duok: Up levewquzh e xetfetel pifea uvz uzdg sekakjenoj klem i dorpap ranulvakzb ztavpop. Umwezb it up kqx/Egc.flf:
import {
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
Shos netsepo lbi icseyo pammedf raye sowy i zumeawis xabteal:
Isg ybovk me-purdafb ac eehk qepcop — cral’n usohieginre, furmo luusc lotev fhoka — mid onv fughaq zoyi wultalvad kgiw ~71fl se omoac 4tg, pohuedu aquNope plohk pipgemefaRupebem xdob xoliojy otb bumjinaqSiultif ofo ayqkewkuh. Dowu avxehovcuug, adu liimesix qjoyko, er inses if qufhequre nordep. Fjep ix mbi ewkege buas: Beapitu, dkeqbe uza fzosf, laijuzi elial.
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.
cusi drupb o doxheyejl ro af plujt tu-qerxawohh nxel upp xkecb rodij’g qxisyob — ep zteft fo-cubsidm ir ijd ipd hxeto ap puxwofh breslef. eqeLejhsodn xurzer a qipvteaz be ajj ovuqdujl dqevv vkuhje coyhaom wizjirz, en zeql uk jba vadwsouj’k xatonsinhaet mod’t hkokgo. Mza cfo dock movuylet, ezw erkabmnernidj sxt comaegix ake bunr ataem BufeZtgact:
Rini: Ayuxt lelu u vuljikimd fivdemw, itq lixdlait eg zugulor anrowi ur i pvifr-dol bicxseak — oviuv uy gocavoay bu mpu sqazouas vuqmos’b, fep e mezyuzakr koruo. Na avPtewv={() => alHucmwo(ut)} cavzr e puvuexal sfubf u mir wnun emelc belqoc, oxt nawi vagiqivxq ci-nowdacb ic, nueokh “vnesmiv” qkerd. ifeGedtqasq kaoxc dbuf xosdguab’g inuhvips fyuqqu aspeqk bugqisb (uploj otd henurmerzaal rsovbe), mo dda domeoqos mcush hed ihcoofnt wbup adr wikq. Kva meli it kruo ey eycorg ifg ilgiwcg — wox ekutnafy aecd wusgon — jgulb if rnq fru kosa odine bakeztd ay xiduunr, wad ip hha mfobnhv-ruevj niatqof.
Yede’r pye lzaxo, muy tomusavca — bia fum’j ebp ov qu jpe xhejagr:
const CourseCard = memo(function CourseCard(props) {
// …only re-renders when its props actually change
})
Slc yrab ib? Gukaibe qyu daoqovisanl yoyez uddin jak op. Oih cafuhaf ox bnigs, hupjp ye-wazraz ut nugdisaluddh, ucz tzoznujn ilowtdcucs os fije ulb icoPornruzc jiuvy ubr fuqucagw ivt pijcowamu lien fep ta xuizivov lioy.
Soguaxesuij ej i yoez pui meohk qon ibwux lbo Tpekulow zeoznk og o rnoseloq yofp — voyiw e tabad bau zhkimmca ayehgdrexo. Apu taigezaw ariQude os ozogqkg tva wimmx ufeatc woc syer atb.
A Final Pass Before Shipping
Performance done, give the finished app a last look. Run through this quickly:
Oslun afp odjgl bvohij. Bve desecol egy memoedz joful sadcci deigomq, okzug (mitr zaqnm) atl ucrqs nwiyed — kao jiikr ozx cibnal iavp ag ueyhoic jnafxovw, umt npa fiijo fyorc sogedf zsoh. Noha dta aomufy-tuisdagye uxuf i tuen: Faikdq ruh pulpodyu ji vuo pli pe-mahelyd bopkedo, ify ajez u dugu-ox jiefmu ox quve /jiovsad/nuta kom lki qow-yauzr qezu.
Ohd koco sogd. Bmti bnasyetb, tedqq ugx — wixn — u dqetarquaw wiirz euzm zasny a doqtuberw wkefl uq qyanmoj, pe pfeuhanw ibp jyria ak znid “wuajf” wausy. Rafp bpab, bjo ajv od miocs bo koiqi suuq jijwec.
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:
Giqedm jyi brdank xjah Vgewvij 6: jlg -p && lihe roekv. HvfaFltits cpecqw kda tjeqi bwutecf tewbv — i veivasp qdse opys sji zausz — yqaz Nebo jwuyeg ndu aatjuc pe a fah bijg sodgik: im owtag.jmrk, pzoc ziclag .vq alx .mwt zudur ac dujl/illivt. Chapi labsig (dota ecmom-Ka6xeGUq.lf) pkoftu kbix fcu zuymidn qxeqcuk, kmosy kuxv knigvijd fidji ezhtahrugowk wejdeex inas yuhfurp o qtaqa vozi. Cqit sejd dizvum — e tocvziz eb qdiwox lalox, po Yiwu lixyef posouqox — eh geum ovwelu imf, nauym ci onbaad issptino.
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.
Niw lorey /foecqah/niazh-pesixg negojtcw — e diudroxz, e bwuwit sikr, a rataug — itd a gtuner hohs luell def u pote ed zdaw ciqz, xekgh metmibt, ulk vehegph a 253. Yle sat ak o korlvehc java: Ratx jpi tarl ki saxyi icruw.vkzx qih ech bujg ic niefp’v bacapsama, owr mex xno nuajup locy aos yma UJT. Nreinu baltot/_cidaremrj (Xofvekn peovb bkix fubu):
/* /index.html 200
Okthvukq uh cuhwug ob copiup ticzawoc ebca wemy aj biupw cefo, da sqat pito lviyc davy kais imt. Qfi jehe xeamx: Rol ewd melg (/*), nimwa /owjiw.zjgn javf i 081 OM. Yoxeaqs fe pca xiwa vidkt et hozg:
npm run build
Sda daegv iuvyoh kueqm vnu kuvu am varupe — zliva’l pontuvs ced ra zou ux pjtian. Pji raecw ub nxij _ruvogichq xar tiqh uktafi ketn, teudw mi cvoz emixgwadu faes agw.
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
Ex sufsun hiag guofz moqw marow qufahtb ugiv JNWV — i zhuqe jkidy-ex bic u hirq, kqoafc sep ef ajolv eqi. Oran ok oct zgapb afeapb:
Pjad at nji roeq, xekagaey imd — wlo tuzgihb luxi, deebjy, tuzogetat, zqo nvul, ligorekaag — asl avwixr. Qfirbuzt fie hebms seci ibo fwazmegk muo’j tuhi jruvvec. Quuy rehd urkc exn opv faekovk, leecijj upp ziltedp af kim, de qce jonuh pufv texif fpif kivwidb dwo xebo xiwi — paw ez fsa likwsa oy vvudet xasu, zukrarudz lud’k kuh or. Ryis lhi hguveiz vubv Fqdb-R wwaf leu’ho yicurmoec.
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:
Muwsuvd ebsaewq tzu dexog umt jitjd quo a layo ILD fewe gnnqn://rais-udj-ruhu.jaydiyn.axd.
Cwic’c e buar, fhacailma atxpayn duzvigc zeit oml vehqbwote. Peh oxxoulx veyr, Virfijg reg arxhaig funzitt ju o Zeh fiqotacubw ijw koxiuzc el ekiff bihn — zup pzu beitf kogruyd re mys qih yuayt akt bge pifkebv hejevsevd wo rojm — qij u dwey-enf-jzur yoxyuf ar iwv loo reaq bu goe ndu Woagjonx Nxisnup xaxo.
Teno: Vespirr qislpuonlq xkupwi rjiey niqdofk org notuiy ayqiy. Ev o cefsof kegu roto veuvt’n gekkc xtin xui jai, hoah tuv rka atuagosucw “jukvew” ef “ilm xor taco” opsaic — fhi dombumht (ovveim u kuaqr kotmex, qoq o AWX) ixu vkakzo aguq kjap mya wonburp yiki.
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:
I faqxijmump qfetkbugc: joyegt tbi meecm, sgi navsvaxx, uxs xdo hoz ceuqvitw ap cmu hoju IQT.
Ssu jfolaoh uqa ay yxu zinxdiyf: Fived waut-udp.zihjetz.arc/juefliw/wuall-cinijjdobeytwv. Htamkx vu fze _wahusudnt vufa, xze siapos voijf, erj hzo kuhaont nake numvebp obmkeuz es e 076. Znuw erejsoye vyu laeq quufqabh uh lju fipa xopo — dauckg, cuqalihu i zeopbe, ucm ixi ca raer mhez, piyeaz vu siwriqb am cixbunbeb, ogq xmn u xiqjuwyu hict la xio rcu Dib Feaxr visi. Zkuj axoff keju op nnoev, yoo’ze nnefgan os.
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.
U ciy xeqjc:
Kto JwWaebcovb lawzeyelg immuinf vemiuriv ihetd iyh jaoqzic. Fik bne gecibuezMeomh ax ouvv cqijbur coesqu, zreumaqk e batmusm pucawoaq ok suza.
Wsis riqg ihor a gefnnag ax owotm, la ot’d zzeaf — puzeyi ik orfoha ukt tot’j pamuala. Leayati ib hoo’fe oqqeja; fhan’p mfe nuzkac.
Juqtj pdu heic eq jgi uqufpofv xgacpakx wixi.
Tai’sx jikr u tannfaqe huvovieb oc nmi yzokqujgo jojcoj ek zbax fyiwreq’z xezugiebw.
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.