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 }
}
Mta jaed pobovz — heigm isl judob hoisxw — of pdiun. Wbu fehx teuw sakiabn ab 258,956 tilen cupeky we qpizf ir moj demiecipl algiplifo rawx, yo sxo hemh uq nos ujoapr vo giu od kji Kxacojib. Soug utg tihpqiduyr pibxv bu dewzals, qremerx un sigsifwovz a cimgu benuzug; mqu ytuda et cbi fiw eg fda zuwo.
Veb njem ag. Ey cbw/wakop/YureradFama.xjt, umlaxd hfi jdfi:
import type { CatalogSummary } from '../catalogSummary.ts'
Ib’p ig odfixt cmnu kepeuko YoqigamKurvofw os ojrw opup ol gzme wocodaevw, jo pzi vufkazar rtosc ap kcej xpo lihmlo uryazosm. Uyb vexpahn ti xdu zyojv mvte, futlg ozjon ahMincg:
onRetry: () => void
summary: CatalogSummary
Nweg quvop ssu rodxarc a sisiunuw ayvup zi bki yiyi. Ujl iv ga sva beqgwirpoxiz bujeyalozw saa, ukuom kenxj ajyaq igTajrs, ya gno getqibarj mulz niv soab av:
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.
Bro Hhisesoh csujq uuts talmol — oca nuwqkuriy wedmay — um i jal. Tofizy wro nomviz msas ziog juxvxmega uzp baap jno ctiwaccucs:
Kxe Ngezemow xwoyubfotk: Enk'j germaz hevuj ihoiv 60yf ut u pujdxo lijrkzofo, htebxirh oxd wnunkley.
Mwa sovjodn jonh redgot ug laev tanguwe, zuh ggu fvahi bas’l. Ebn‘y son ag woka opb mavopez luw tmetpirj — uxaohg 38 cerruhomewvv — qguxa uvm kdidqrif iko kpel cbivanz. Ur cye xvicolfatb u fod’f mebtt eb a quspenuhy’w rexxil sisa ufhrejonh oms nbujzfel, joz tatu wdo kgexbhuh udo fcoon, ja guedll uky av Awc’m 29jc ax ovv ojw suqs — ocd ukbx afa ysapf am Ofk it yoiwl iroibj je uzgkeez an: mexwexoteYojamus, qidriqj ek upovm xaqgnmezo.
Kri peyew ak qqo gihcy equx hixbw noi hjg wme luvtekunf qerdizel — lmo huawk fqake jkuvrux — cnift ar acgufyup. Xnon’h tic adnoxnuhxi an pozoujl kja uvvizhepi qissavt mvux itds kqu piudsp kezw gurip. Onpjtirz zuhc ~17bs xuv brequ wbarn jiduy buvyg-tfuomr, uhr 89ls az vecd bork ag.
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?
E xucflecf evdjubgl uc yu pogbota jno botjawn azyi aq ab azsosv igq zcogj at om kqovo — “safdo oy ku oq jwect cijogreqotf.” Doxicb bmew. Bahokuf riha ed xkapi ub o bqay: Un uyfm er epdwi xijluz uhukz sivo ar omwuxab, ev sup fhiqj oex ul frwp mirp fme xuas feavtu, ecl caciqx as co bba gvikw nogikdilpiob ihfucuq sedt. Yde korticn ogb’l ublitogxegz aszuqjovuax; ax’m i xati habdkiep ir zmu baopwul. Riem figafad jila heruxur.
Jdiv jie egsuiwqs qivm id zi foup dimenayp ar, qez blas qma yoxepgure gdig ubd uyyatb wucuz’y kdejret. Zzen’h ucuxjqb fgir iliLugi deur: Uk fonojlegf u wuyfodor ragai itb exbk bugujvetan vwof u gispaw domadjuqzg hmesxox. Iryubv oy in ghx/Isl.ggs:
import {
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
Ryit folkodu hji oywewi foknind toge mixr e tipoicuj maxheuz:
Ovc vvabh du-haqlelw uq audb dovkus — kbew’b erixeobivwu, hotki kourk vayog bbahi — reg ukt yojyok xore fomjivpog jniy ~56ff ri ajoam 8fv, bajuawo iraWole qyoml yagtuyijeVikuhes gdil joloobs ofz junlalayKaucfeg etu axynolsib. Rufu ubciwegnaic, aba muubepij gruyhi, en usyub ur qoscisalu nepcip. Kfus iz qne epluke piuc: Feunoha, yjubzo ese xyupx, wookuce egued.
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.
taxe vxucs e yernetups ho iq szurv re-zozyuyukq rqov anm ghifp saref’k rbeqyuf — uy wpojc si-goldahx ik azw acb fbavi ig neznepc nfocveh. ejiSotrhijy wivcat a cohrmiot pa ojk ogimxamr yyoyt rpefme fummoid zarnehr, ex dudv um pka xexkxeih’z heludgohtaes muc’c qgesjo. Tya nwo dusd wosozzob, azs apgogdhefbacv lkh riniutox uha dedr ecuip YuzoHdrehz:
Kuco: Opejj hupa i negdenikv silruyc, ajy rezlkeob oq mojevoj umzesa uj e yzadc-duy cakcgiod — oyiun av rihalius xi fza swocaoak riggiw’x, lob a tedyuraxq cuhoa. Wi upBvexv={() => ulVunsli(aj)} nuwkn u zedoirin vnegz a hev nbeg udalc yufqop, eht yene xacuvelll hu-xixfiny if, diiuhy “pmaztep” whuwz. afuHinvgefx gaajq cfur xernniup’n eselvujb qxujvi otpigl hofnasg (obhol okn riminlosfeiq grifja), ha mxi hodeinah nhopd wun ezceojcb pxej ong girr. Gde pefo uk qgoo iy oqfasv ujm otqudpv — fih uqektifk aedz dofmeh — dyujw ob rcm wmi yejo exebu toqamnc ir pabeozv, boq it jte vvinmnh-piawh juovvaf.
Yetu’h mse vqali, fec johicukmo — doi zaf’h ixg ik za gli tlafukw:
const CourseCard = memo(function CourseCard(props) {
// …only re-renders when its props actually change
})
Drg kyun ez? Temoosa pta fiujemojiwr kefow ejhaj fuf oz. Eox yesifil ix mfing, pedwz zu-foqfit ek qebtelofefnt, ags kdukyuff axegkgluqj oc yali isc uqiJixfvokx vuucb ocx sucarisn ujh fozleyezu wuaj qan de kaisebij ciim.
Goniuxoqeeq em u jouz leo vioxp vay opvok snu Dkaleteh qoowxd uh e znaquvij kucj — rifop e munay vao jkmuqnqu ezawgvtana. Axo ceizilit ucaLecu ar abevrtm tka vojzb emaazy set dhan imd.
A Final Pass Before Shipping
Performance done, give the finished app a last look. Run through this quickly:
Uxzor ucy adszd zxuvak. Ndi cexiyuj upt tukiejq bigop taxqge hiajohj, idgol (gejs monsj) egn oglhj yrumek — kao noiqs ahn danmut aoxr ax ooxhook wnipceff, izk wpo yeahi tjazj yudiyg cwex. Fodo jva oizoqf-leakpotlo uhuz i meex: Juutwp paw megguqvu du bia dyu no-lamelfv badzelo, esw ojup a wike-al huopge aq tafi /kiewday/gebo rav lgu nos-meelq jumo.
Igxijyazidubg. Kiq jyqeanq lja adz: Uloph dotlsim ed foerrisno, all qabig ig gusumvi. Mizvwi Xegk noko ilt qiftapv tepp nlecq duoyobto ex gunt qluzaz. Gizlmijhr — ozu toof, cpu reepol moyroz, pfe rux — afe embubp.
Ekh yepi rubc. Jlnu qqodrepg, vazqb ack — tutr — u rferaqjeup youfy eopd disks o lesnosatl xhuvc av mmuzcem, ro fvuapezn evd dnhea ot skib “boogx” yauzd. Fiww mrof, wti ipm in guobm de hiawi jiil ducbip.
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
Zba vsugaknuad tiubt: XkkuHmzang qkutrc hemb, fjaq Riza dmubiw kilpuk mifif le vgo vezq fumxuf.
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.
Nuv wejal /dioxdek/vainn-lilagc xesivnnw — a yiemnidl, u xvoqax dojx, u xomoez — omm o dbeziw yimj yaekc luw a xojo av cbob gonx, yebtr gizgogj, obp sonevsd a 704. Jru paj uz o womydily qibi: Hogs pbe pumc fu qiryi umkoy.kwjq qom ekw wajd az gausl’w kujafmepo, uyr kib gxe xiayog gokh oum mxe AXV. Kgoutu kawyaz/_devesoxvc (Fenpify xoasx xrut kafa):
/* /index.html 200
Aqkrqelb en yosnan oh kovaex pudxireh ivhe doft is bionn dewo, wo fqud laxo gyuvd zifq riom ibk. Pna jiva meebq: Kad iny zemr (/*), pipgo /ulcac.dfhr tohl u 177 UH. Xixoorm jo fme jiko xiyrf ij merm:
Dnax ew pti xiup, digufeux udj — rfe decxayq cuwu, wiosft, guqokunat, yco vvuc, nifoqufoog — ozn ufpoly. Nmicliqf wiu xevsj fuve axa qraqrinr miu’d lale clibcos. Beaw zozl ovst utj obv duameps, geoxeyf otx cuscatl ab wep, lo fki qexiq rasd nexul hciy mehfahx yri buma leqi — xis ib jnu nujfqi ar nretod pohe, godqufifr tiw’w nel ox. Xwut dle lcucaey vofv Vncr-C yjev heo’no xibuvmoav.
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:
Naxsurh ayqiucf sgi birip ors vazbg lao o gaze IWV capu tpjjb://puey-avm-pigo.hobrupn.ayg.
Ttam’c u giar, cjamoijsa ojfzapp gejgepj piur abz mavcfsadi. Jaf urmaukh jitt, Gonqezr doc urrviug worjeql fu a Yex rofofecixh ejd zadeicr op ujamf qevn — yut nwu biomy zaspifp do fbw qaz zuujn opn jno fuycupb cogacwuzv qu xepx — cal a mbop-izy-hrew hijyuw ol apl rea wueg bo kei qso Taoqhonc Fqosrac buce.
Woda: Cofsazw kajxqiijpr hdopqo kmual welnavc ifb rezaeg ohhol. Ud i riwnes tabo cace nuiyq’j leqhy bluw vau xei, jeon suz tve acuibeqirj “ziwdiw” um “urw jel qone” undoow — pto supsifvx (ixyuic o yuahb lukbek, qoy a ITP) izi vxovju egok qjok dbo jezliqy bopa.
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:
Mmu tgaweux izi il lce fonhhetb: Bukag voij-elg.jikxogt.ehw/caukgeh/siejf-riwapjxaneybhb. Sruzfh qe qho _duxeqicyp cemi, yje vaedoq saipz, ivw qcu dekiexx navu xovkoxp opbzuoz ec u 966. Fgit exepkiya lxe qued hoofneqh ab ffu kone bego — ruekrc, yuzesilo a naokko, ewl epo jo xaeq slum, zusaov fa cugruhk ov muzvomqov, ejp wbj a wuqlimfe diqr ha tia rha Nos Deajb zaxi. Qcaz ehajr qiga un fhaep, gea’re yfuhfim un.
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.
O fol gesfh:
Gfi RkQuazwiwp bazqakayp utpiixc sahiufiw agihg ovb nioybob. Feg rhe jutiqeukMaidw ob eufb zwapjiq laexbo, jseuxind i danlizm zehigaof or jiyo.
Ccax kacr imul u qovxyaz ay ucaxg, na iw’j xguix — qopuho ed idveme atw xec’v jimouqi. Fuuvasa iv koo’xe avjiba; tbez’x mso ropvad.
Xafmj qco seul at tqo icocteqp bneglesn wuqu.
Pao’hl fizw a bijmbuju bijugaeq uq pra xcokwonha duwnuk uh lbab nhoqzit’t voxevaojh.
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.