ЁЯПл The SchoolтА║ЁЯУМ UIтА║ЁЯЪЪ рдзрдбрд╛ 12 тАФ Build, deploy рдЖрдгрд┐ observe: рдлрд▓рдХ рднрд┐рдВрддреАрд╡рд░ рд▓рд╛рд╡рдгреЗ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЪЪ рдзрдбрд╛ 12 тАФ Build, deploy рдЖрдгрд┐ observe: рдлрд▓рдХ рднрд┐рдВрддреАрд╡рд░ рд▓рд╛рд╡рдгреЗ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 12 тАФ рд╢реЗрд╡рдЯрдЪрд╛ рдзрдбрд╛! ┬╖ рдорд╛рдЧреАрд▓: lesson-11-talking-to-backends


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рд╕рдВрдкреВрд░реНрдг рдХреЛрд░реНрд╕, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рд╢реЗрд╡рдЯрдЪрд╛ рдЯрдкреНрдкрд╛: build рдХрд╛рдп рдХрд░рддреЛ, рдлрд▓рдХ deploy рдХрд╕рд╛ рд╣реЛрддреЛ (рддреНрдпрд╛ рдлрдХреНрдд files рдЕрд╕рддрд╛рдд), рд▓реЛрдХрд╛рдВрдирд╛ рдирд╡реАрди version рджрд┐рд╕рд╛рд╡реЗ рдореНрд╣рдгреВрди cache busting, рдЖрдгрд┐ рдЦрд▒реНрдпрд╛ users рдЪреЗ рдирд┐рд░реАрдХреНрд╖рдг тАФ рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рдХрд│рд╕-рдкреНрд░рдХрд▓реНрдк (capstone).

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдлрд▓рдХ рддреБрдордЪреНрдпрд╛ рдЯреЗрдмрд▓рд╛рд╡рд░ рдЪрд╛рд▓рддреЛ. рд╕рдЧрд│реНрдпрд╛рдВрдирд╛ рджрд┐рд╕реЗрд▓ рдЕрд╢рд╛ рднрд┐рдВрддреАрд╡рд░ рддреЛ рд▓рд╛рд╡рдгреНрдпрд╛рд╕рд╛рдареА рддреАрди рдЧреЛрд╖реНрдЯреА рдШрдбрддрд╛рдд:

  1. Build ЁЯПЧя╕П тАФ files bundle рдЖрдгрд┐ minify рдХрд░рд╛, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХреАрд▓рд╛ рддрд┐рдЪреНрдпрд╛ content рдЪрд╛ hash рдЕрд╕рд▓реЗрд▓реЗ рдирд╛рд╡ рджреНрдпрд╛: app.7f3c2a.js. (рдЖрдкрд▓реНрдпрд╛ рдлрд▓рдХрд╛рд▓рд╛ build рдЕрдЬрд┐рдмрд╛рдд рд▓рд╛рдЧрдд рдирд╛рд╣реА тАФ рддреАрди рд╕рд╛рдзреНрдпрд╛ files тАФ рдЖрдгрд┐ рдЫреЛрдЯреНрдпрд╛ рдлрд▓рдХрд╛рд╕рд╛рдареА рд╣реЗ рдкреВрд░реНрдгрдкрдгреЗ рдпреЛрдЧреНрдп рдЙрддреНрддрд░ рдЖрд╣реЗ.)
  2. Deploy ЁЯЪЪ тАФ files static host рд╡рд░ copy рдХрд░рд╛ рдЖрдгрд┐ CDN рд▓рд╛ рд╡рд╛рдЪрдХрд╛рдВрдЬрд╡рд│ рдкреНрд░рддреА рдареЗрд╡реВ рджреНрдпрд╛: GitHub Pages, S3 + CloudFront, Netlify, Cloudflare Pages. рдлрд▓рдХ рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд files; рдЪрд╛рд▓рд╡рд╛рдпрд▓рд╛ рдХреЛрдгрддрд╛рд╣реА server рдирд╛рд╣реА.
  3. Cache busting ЁЯзК тАФ HTML рдереЛрдбрд╛ рд╡реЗрд│ cache рд╣реЛрддреЗ, hashed files рдХрд╛рдпрдордЪреНрдпрд╛. рдирд╡реАрди build рдирд╡реАрди рдирд╛рд╡реЗ рдмрдирд╡рддреЛ, рдореНрд╣рдгреВрди рдХреЛрдгрд╛рд▓рд╛рдЪ рдХрдзреА рдЕрд░реНрдзрд╛-рдЬреБрдирд╛ рдлрд▓рдХ рджрд┐рд╕рдд рдирд╛рд╣реА. (рдореНрд╣рдгреВрдирдЪ рдзрдбрд╛ 09 рдордзрд▓реЗ "рд╡рд░реНрд╖рднрд░ cache рдХрд░рд╛" рд╕реБрд░рдХреНрд╖рд┐рдд рдЖрд╣реЗ.)

рдордЧ рддреБрдореНрд╣реА рд▓рдХреНрд╖ рдареЗрд╡рддрд╛ ЁЯСА: рдЦрд▒реНрдпрд╛ browsers рдордзрд▓реЗ JavaScript errors, рдЦрд▒реНрдпрд╛ phones рд╡рд░рдЪреНрдпрд╛ рдЦрд▒реНрдпрд╛ users рдЪреЗ Core Web Vitals, рдЖрдгрд┐ рдХрд╛рдЙрдВрдЯрд░рдЪреЗ calls рдХрд┐рддреА рд╡реЗрд│рд╛ рдЕрдпрд╢рд╕реНрд╡реА рд╣реЛрддрд╛рдд. рдХрд╛рд░рдг рдлрд▓рдХ рддреБрдордЪреНрдпрд╛ рдорд╛рд▓рдХреАрдЪреНрдпрд╛ рдирд╕рд▓реЗрд▓реНрдпрд╛ рд╣рдЬрд╛рд░ devices рд╡рд░ рд░рд╛рд╣рддреЛ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    src["ЁЯУБ ui/index.html ┬╖ styles.css ┬╖ app.js"]
    build["ЁЯПЧя╕П build (optional here)<br/>minify ┬╖ bundle ┬╖ hash filenames"]
    host["ЁЯЪЪ static host + CDN<br/>GitHub Pages ┬╖ S3+CloudFront ┬╖ Netlify"]
    cache["ЁЯзК HTML: short cache ┬╖ hashed assets: 1 year, immutable"]
    obs["ЁЯСА observe<br/>JS errors ┬╖ web vitals (RUM) ┬╖ failed API calls"]
    src --> build --> host --> cache --> obs
    obs -->|"what real users hit"| src

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдХреЛрдгрд╛рд▓рд╛рдЪ рдкреЛрд╣реЛрдЪрддрд╛ рди рдпреЗрдгрд╛рд░рд╛ рдлрд▓рдХ рд╣рд╛ рдлрд▓рдХрдЪ рдирд╛рд╣реА, рдЖрдгрд┐ рдХрд╛рд░рдг deploy рдЪреЗ рджреЛрди рдиреЗрд╣рдореАрдЪреЗ bugs рдкреВрд░реНрдгрдкрдгреЗ рдЯрд╛рд│рддрд╛ рдпреЗрддрд╛рдд: рд▓реЛрдХрд╛рдВрдирд╛ рд╢рд┐рд│реЗ рдкрд╛рди рджрд┐рд╕рдгреЗ (рдЪреБрдХреАрдЪреЗ cache headers) рдЖрдгрд┐ рдХреЛрдгрд╛рдЪреНрдпрд╛рдЪ рд▓рдХреНрд╖рд╛рдд рди рдЖрд▓реЗрд▓реЗ рдореЛрдбрд▓реЗрд▓реЗ release (error reporting рдирд╛рд╣реА). рджреЛрдиреНрд╣реА рд╣реБрд╢рд╛рд░реАрдиреЗ рдирд╛рд╣реА, рддрд░ settings рдиреЗ рд╕реБрдЯрддрд╛рдд.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

рд╣реА рд╢рд╛рд│рд╛рдЪ рдпрд╛рдЪреЗ рдкреНрд░рд╛рддреНрдпрдХреНрд╖рд┐рдХ рдЖрд╣реЗ: рдкреНрд░рддреНрдпреЗрдХ course site рдореНрд╣рдгрдЬреЗ GitHub Pages рд╡рд░рдЪреНрдпрд╛ static files, push рд╡рд░ deploy рд╣реЛрдгрд╛рд▒реНрдпрд╛. рддреБрдордЪрд╛ рдлрд▓рдХрд╣реА рддрд╕рд╛рдЪ рд▓рд╛рд╡рддрд╛ рдпреЗрддреЛ тАФ framework рдирд╛рд╣реА, server рдирд╛рд╣реА, рдПрдХ command.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛ тАФ рдХрд│рд╕-рдкреНрд░рдХрд▓реНрдк (capstone)

# 1) deploy the board (5 minutes, free):
#    - copy ui/ into docs/app/ of your fork, commit and push
#    - GitHub тЖТ Settings тЖТ Pages тЖТ Source: main /docs
#    - open https://<you>.github.io/<repo>/app/  тЖТ your board, on the internet
#    (the counter is local-only, so point API at a deployed one or ship a static fallback list)
# 2) see caching in action: DevTools тЖТ Network, reload twice тАФ the second load shows "(disk cache)" for styles.css
# 3) add crash reporting in 6 lines, in app.js:
#      window.addEventListener('error', e => navigator.sendBeacon('/api/log', JSON.stringify({msg: e.message, src: e.filename, line: e.lineno})));
#      window.addEventListener('unhandledrejection', e => console.warn('unhandled', e.reason));
# 4) measure a real vital in the browser:
#      new PerformanceObserver(l => l.getEntries().forEach(e => console.log('LCP', Math.round(e.startTime), 'ms'))).observe({type:'largest-contentful-paint', buffered:true});
# 5) ЁЯПЖ THE CAPSTONE тАФ add a homework list to the board, end to end:
#      HTML section with a heading and a list (L02) ┬╖ layout in the grid (L03) ┬╖ state.homework + a component + render (L04тАУ05)
#      ┬╖ labels, live region, keyboard pass (L06) ┬╖ tokens only, works in dark mode (L08) ┬╖ lazy/sized images if you add any (L09)
#      ┬╖ three new smoke checks (L10) ┬╖ fetch from /api/homework with loading and error faces, textContent only (L11) ┬╖ deployed (L12)

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

рддреБрдордЪрд╛ рдлрд▓рдХ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ URL рд╡рд░ рдЙрддреНрддрд░ рджреЗрддреЛ. рджреБрд╕рд▒реНрдпрд╛ reload рд▓рд╛ CSS disk cache рдордзреВрди рдпреЗрддреЗ. Console рдордзреНрдпреЗ рдХрд╛рд╣реА throw рдХреЗрд▓реЗ рдХреА error listener рдЪрд╛рд▓рддреЛ, рдЖрдгрд┐ observer milliseconds рдордзреНрдпреЗ LCP рдЫрд╛рдкрддреЛ. Capstone рдлрд▓рдХ smoke_test.py pass рдХрд░рддреЛ тАФ рддреБрдордЪреНрдпрд╛ рддреАрди рдирд╡реАрди рддрдкрд╛рд╕рдгреНрдпрд╛рдВрд╕рд╣ тАФ рдЖрдгрд┐ homework рдпрд╛рджреА рджреЛрдиреНрд╣реА themes рдордзреНрдпреЗ рдЖрдгрд┐ рдлрдХреНрдд keyboard рд╡рд░реВрдирд╣реА рдЪрд╛рд▓рддреЗ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рддреБрдордЪреНрдпрд╛ laptop рд╡рд░рдЪреНрдпрд╛ рддреАрди files рдкрд╛рд╕реВрди рддреБрдореНрд╣реА рдлрд▓рдХ рдПрдХрд╛ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ, cache рд╣реЛрдгрд╛рд▒реНрдпрд╛, рдирд┐рд░реАрдХреНрд╖рдгрд╛рдЦрд╛рд▓рдЪреНрдпрд╛ URL рдкрд░реНрдпрдВрдд рдиреЗрд▓рд╛рдд тАФ рдЖрдгрд┐ рдХреЛрд░реНрд╕рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдерд░рд╛рддреВрди рдПрдХ рд╕рдВрдкреВрд░реНрдг feature рдЬреЛрдбрд▓реЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдХрдВрдкрдиреА рдХрд░рддреЗ рддреНрдпрд╛рддрд▓реЗ рд╕рдЧрд│реНрдпрд╛рдд рд╡рд╛рд░рдВрд╡рд╛рд░ deploys рдореНрд╣рдгрдЬреЗ front-end deploys. Static hosting, hashed assets рдЖрдгрд┐ real-user monitoring рд╣реА рдХрдВрдЯрд╛рд│рд╡рд╛рдгреА рд░рдЪрдирд╛рдЪ рддреНрдпрд╛рдВрдирд╛ рдШрдЯрдирд╛рд╡рд┐рд░рд╣рд┐рдд рдмрдирд╡рддреЗ.

ЁЯОУ рдлрд▓рдХ рддреБрдордЪрд╛ рдЖрд╣реЗ

рд╕реВрдЪрдирд╛ рдлрд▓рдХ тЖТ semantic рд╕рд╛рдВрдЧрд╛рдбрд╛ тЖТ рд╡рд╛рдХрдгрд╛рд░реА рдорд╛рдВрдбрдгреА тЖТ events тЖТ рд╕рддреНрдпрд╛рдЪрд╛ рдПрдХрдЪ рд╕реНрд░реЛрдд тЖТ рдкреНрд░рддреНрдпреЗрдХрдЬрдг рддреЛ рд╡рд╛рдЪреВ рд╢рдХрддреЛ тЖТ frameworks, рд╕рдордЬрд▓реЗрд▓реЗ тЖТ рдЧрдгрд╡реЗрд╢ тЖТ рдЬрд▓рдж тЖТ test рдХреЗрд▓реЗрд▓рд╛ тЖТ рдХрд╛рдЙрдВрдЯрд░рд╢реА рд╕реБрд░рдХреНрд╖рд┐рддрдкрдгреЗ рдмреЛрд▓рдгрд╛рд░рд╛ тЖТ рднрд┐рдВрддреАрд╡рд░ рд▓рд╛рд╡рд▓реЗрд▓рд╛ рдЖрдгрд┐ рд▓рдХреНрд╖ рдареЗрд╡рд▓реЗрд▓рд╛. рддреБрдореНрд╣реА рдлрдХреНрдд UI рд╢рд┐рдХрд▓рд╛ рдирд╛рд╣реАрдд тАФ рддреБрдореНрд╣реА рдПрдХ рдлрд▓рдХ рдЪрд╛рд▓рд╡рддрд╛. ЁЯУМЁЯОУ

рд╕рдВрдкреВрд░реНрдг рд╢рд╛рд│рд╛, рд╕реБрд░реБрд╡рд╛рддреАрдкрд╛рд╕реВрди рд╢реЗрд╡рдЯрдкрд░реНрдпрдВрдд: Database рд╢рд╛рд│рд╛ рд╕рддреНрдп рдЬрдкрддреЗ, API рд╢рд╛рд│рд╛ рдЪрд┐рдареНрдареНрдпрд╛рдВрдирд╛ рдЙрддреНрддрд░ рджреЗрддреЗ, рд╣рд╛ рдлрд▓рдХ рддреЗ рд▓реЛрдХрд╛рдВрдирд╛ рджрд╛рдЦрд╡рддреЛ, рдЖрдгрд┐ CI/CD рд╢рд╛рд│рд╛ рдкреНрд░рддреНрдпреЗрдХ push рд╡рд░ рддреЛ рднрд┐рдВрддреАрд╡рд░ рд▓рд╛рд╡рддреЗ.

git checkout main
python3 ui/mock_api.py     # one last look at the board

ЁЯЪЪ Lesson 12 тАФ Build, deploy & observe: pinning the board up

ЁЯУН You are here: Lesson 12 of 12 тАФ the final lesson! ┬╖ Previous: lesson-11-talking-to-backends


ЁЯУж What's in this branch

The complete course, plus the last mile: what a build does, how a board is deployed (it is just files), cache busting so people see the new version, and observing real users тАФ plus the capstone.

ЁЯзТ Explain like I'm 5

The board works on your desk. To put it on the wall where everyone can see it, three things happen:

  1. Build ЁЯПЧя╕П тАФ bundle and minify the files, and give each one a name containing a hash of its contents: app.7f3c2a.js. (Our board needs no build at all тАФ three plain files тАФ which is a perfectly good answer for a small board.)
  2. Deploy ЁЯЪЪ тАФ copy the files to a static host and let a CDN keep copies near readers: GitHub Pages, S3 + CloudFront, Netlify, Cloudflare Pages. A board is just files; there is no server to run.
  3. Cache busting ЁЯзК тАФ the HTML is cached briefly, the hashed files forever. A new build makes new names, so nobody ever sees a half-old board. (This is why lesson 09's "cache for a year" is safe.)

Then you watch ЁЯСА: JavaScript errors from real browsers, the Core Web Vitals of real users on real phones, and how often the counter's calls fail. Because the board lives on a thousand devices you do not own.

ЁЯЧ║я╕П Diagram

flowchart LR
    src["ЁЯУБ ui/index.html ┬╖ styles.css ┬╖ app.js"]
    build["ЁЯПЧя╕П build (optional here)<br/>minify ┬╖ bundle ┬╖ hash filenames"]
    host["ЁЯЪЪ static host + CDN<br/>GitHub Pages ┬╖ S3+CloudFront ┬╖ Netlify"]
    cache["ЁЯзК HTML: short cache ┬╖ hashed assets: 1 year, immutable"]
    obs["ЁЯСА observe<br/>JS errors ┬╖ web vitals (RUM) ┬╖ failed API calls"]
    src --> build --> host --> cache --> obs
    obs -->|"what real users hit"| src

тЭУ What

ЁЯдФ Why

Because a board nobody can reach is not a board, and because the two classic deploy bugs are entirely preventable: people seeing a stale page (bad cache headers) and a broken release nobody noticed (no error reporting). Both are solved with settings, not cleverness.

ЁЯФз How (in this repo)

This very School is the demonstration: every course site is static files on GitHub Pages, deployed on push. Your board can go up the same way тАФ no framework, no server, one command.

ЁЯзк Try it тАФ the capstone

# 1) deploy the board (5 minutes, free):
#    - copy ui/ into docs/app/ of your fork, commit and push
#    - GitHub тЖТ Settings тЖТ Pages тЖТ Source: main /docs
#    - open https://<you>.github.io/<repo>/app/  тЖТ your board, on the internet
#    (the counter is local-only, so point API at a deployed one or ship a static fallback list)
# 2) see caching in action: DevTools тЖТ Network, reload twice тАФ the second load shows "(disk cache)" for styles.css
# 3) add crash reporting in 6 lines, in app.js:
#      window.addEventListener('error', e => navigator.sendBeacon('/api/log', JSON.stringify({msg: e.message, src: e.filename, line: e.lineno})));
#      window.addEventListener('unhandledrejection', e => console.warn('unhandled', e.reason));
# 4) measure a real vital in the browser:
#      new PerformanceObserver(l => l.getEntries().forEach(e => console.log('LCP', Math.round(e.startTime), 'ms'))).observe({type:'largest-contentful-paint', buffered:true});
# 5) ЁЯПЖ THE CAPSTONE тАФ add a homework list to the board, end to end:
#      HTML section with a heading and a list (L02) ┬╖ layout in the grid (L03) ┬╖ state.homework + a component + render (L04тАУ05)
#      ┬╖ labels, live region, keyboard pass (L06) ┬╖ tokens only, works in dark mode (L08) ┬╖ lazy/sized images if you add any (L09)
#      ┬╖ three new smoke checks (L10) ┬╖ fetch from /api/homework with loading and error faces, textContent only (L11) ┬╖ deployed (L12)

тЬЕ Verify тАФ what you should see

Your board answers on a public URL. The second reload serves CSS from the disk cache. The error listener fires when you throw something in the console, and the observer prints an LCP in milliseconds. The capstone board passes smoke_test.py тАФ including your three new checks тАФ with the homework list working in both themes and from the keyboard alone.

ЁЯПБ What you just proved

You took a board from three files on your laptop to a public, cached, observed URL тАФ and added a whole feature through every layer of the course.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: front-end deploys are the most frequent deploys a company does. Static hosting plus hashed assets plus real-user monitoring is the boring setup that makes them uneventful.

ЁЯОУ The board is yours

The notice board тЖТ semantic skeleton тЖТ layout that bends тЖТ events тЖТ one source of truth тЖТ everyone can read it тЖТ frameworks, understood тЖТ the uniform тЖТ fast тЖТ tested тЖТ talking safely to the counter тЖТ pinned up and watched. You didn't just learn UI тАФ you run a board. ЁЯУМЁЯОУ

The whole school, end to end: the Database school keeps the truth, the API school answers the slips, this board shows it to people, and the CI/CD school pins it up on every push.

git checkout main
python3 ui/mock_api.py     # one last look at the board
тЖР Previoustalking to backendsFinished! Take the quiz тЖТcheck what stuck

This page is the lesson's README from the lesson-12-build-deploy-observe branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.