OpenVibe.Live/Docs
Source on GitHub

Performance audit — 2026-09-16/17#

Starting point: HEAD 808262d ("Load pastes.js and dashboard.js on demand…"), branch seadragon, clean worktree. Everything below was measured, not estimated; where a number could not be measured it says so.

How it was measured#

ToolWhat it does
scripts/perf/measure-page.js <url> [--runs N] [--phone] [--phone-cpu]cold load in headless Chrome over the DevTools protocol, fresh profile and HTTP cache disabled per run; reports medians of transfer, requests, DOM nodes, running animations, FCP/LCP/CLS, long tasks, TBT, script/style/layout time
scripts/perf/ab.js <urlA> <urlB> --runs Nthe same, interleaved A,B,A,B… so machine load affects both sides equally
scripts/perf/trace-page.js <url>Chrome trace; attributes style/layout work to the JavaScript that forced it
test/browser/smoke.jsroutes × widths, errors, overflow, navigation, duplicate scripts, resource growth over repeated navigation

Profiles: desktop 1366×900 unthrottled; phone 412×900, 4× CPU, 1.6 Mbps/150 ms; phone-cpu 412×900, 4× CPU, network unthrottled.

Caveats that matter:

Baseline — production, https://openvibe.live/, 3 cold runs (median)#

MetricDesktopPhone (4× CPU, 1.6 Mbps)
HTML381 KB raw / 75.5 KB wiresame
Scripts44 files, 1 993 KB raw / 612 KB wire49 files, 2 677 KB raw / 784 KB wire (idle prefetch ran)
Stylesheets12 files, 1 029 KB raw / 237 KB wiresame
Requests10191
DOM nodes7 4487 160
Running animations5320
FCP1 620 ms6 344 ms
LCP5 792 ms9 808 ms
CLS0.0860.133
TBT807 ms2 589 ms
Style / layout589 / 1 085 ms1 807 / 2 012 ms

Results — local A/B, home page, old build vs new build#

Desktop, 7 interleaved runs each#

MetricBeforeAfterChange
HTML (raw)374.8 KB94.9 KB−74.7%
Script files4323−46.5%
Script bytes (raw)1 965 KB1 000 KB−49.1%
CSS bytes (raw)1 029 KB824 KB−19.9%
Requests7755−28.6%
Total transfer3 743 KB2 252 KB−39.8%
DOM nodes6 4021 741−72.8%
LCP5 644 ms2 552 ms−54.8%
FCP2 668 ms2 552 ms−4.3% (noise range)
Script time428 ms338 ms−21.0%
Layout time1 659 ms1 487 ms−10.4%
CLS0.0620.064unchanged

Phone CPU profile, 9 interleaved runs each (final build, self-hosted icons)#

MetricBeforeAfterChange
HTML (raw)374.8 KB94.7 KB−74.7%
Script bytes (raw)1 965 KB1 000 KB−49.1%
CSS bytes (raw)1 029 KB824 KB−19.9%
Total transfer3 743 KB2 347 KB−37.3%
DOM nodes6 3421 732−72.7%
Running animations3021−30.0%
FCP3 260 ms2 792 ms−14.4%
LCP3 412 ms3 024 ms−11.4%
TBT4 565 ms3 846 ms−15.8%
Script time618 ms336 ms−45.6%
Style time2 061 ms1 858 ms−9.8%
Layout time2 717 ms2 667 msunchanged

What did not improve: layout on the home page is dominated by the page's own content (hero, collage, rails) and stayed flat; long-task totals are within noise.

What changed, and why it moved these numbers#

ChangeEffect
Route markup moved to public/fragments/*.html, inlined by the server only for the route requestedindex.html 382 KB → 81 KB source; DOM nodes −73%
public/features.json + js/ov-loader.js: 28 route scripts load with their route (and are prefetched on link hover/focus, download-only)home scripts 44 → 23 files
app.js split by reachability analysis into core (128 KB) + app-home, app-channel, app-media, app-chatpage, app-docs539 KB → 128 KB always-loaded
Stylesheet split by class ownership with cascade-order checks (computed styles diffed on 44 route states: only data-driven differences remain)style.css 666 KB → 484 KB; broadcast.css 100 KB → 51 KB; 8 feature files
A stray } in style.css made browsers drop the whole @media (max-width:1200px) blockfixed: nav links no longer overflow at 1100 px (79 px → 0)
Forced synchronous layouts found with trace-page.js: ov-density measured inside render (≈0.5 s at 4× CPU), odometer reflow per digit, tour diagram wired below the fold, innerWidth reads during loadmeasured in ResizeObserver, batched, wired on visibility, matchMedia
Hero quip and first rotating word shipped in the HTMLLCP element paints at first render instead of after /api/home/hero
Font Awesome self-hosted (woff2 only, content-hashed URLs)no third-party connection on the critical path
Dead theme-engine.js (parser-blocking) removedone fewer blocking script

Server#

MetricBeforeAfterHow measured
Graceful shutdown (SIGTERM → exit)forced exit after ~5.3 s with code 1 (SSE clients kept server.close() open; per audit)1.1 s, exit 0local, kill + timing
Local boot to /api/ready~2.5 s~2–3 srestart script (unchanged within noise)
Event-loop delaynot observablep50/p99/max per minute at GET /api/admin/diagnostics, logged when p99 ≥ 200 msserver/diagnostics.js
Static response for style.css (8 concurrent, Node)67 ms mediancached in nginx after first fetch (X-Cache HIT verified with nginx 1.27)local bench + docker nginx

Deploy#

Change typeBeforeAfter (verified in test/deploy-sim.test.js)
docs / public onlyrestart (legacy script restarted on every commit)no restart; release layout switches current, same PID
server coderestartrestart, readiness-gated, automatic rollback
lockfilenpm ci in place; rollback kept new node_modulesinstalled into the new release; rollback returns the old release's node_modules

Budgets#

npm run perf:budget (scripts/perf/check-budgets.js) fails if the home page's raw HTML, eagerly loaded JS or render-blocking CSS grow past the limits recorded there.

This page is rendered from docs/performance-audit.md in the OpenVibe.Live repository. Found a mistake? Edit it there.