Skip to main content

Performance budget

Every shippable component (widget, Shopify app block, WordPress plugin, SDKs, Python backend) has a hard ceiling. PRs that bust a ceiling are blocked at review unless the doc is updated in the same PR with a justification.

Frontend (widget.js + widget.css)

MetricBudgetCurrent (post Phase 1)Headroom
widget.js raw100 KB~87 KB13 KB
widget.js gzipped35 KB~22 KB est.13 KB
widget.css raw15 KB~8.4 KB6.6 KB
Total CSS+JS gzip45 KB~28 KB est.17 KB
Time-to-first-byte (widget.js from api.genvoris.org)200 ms p75not yet measured
Time-to-interactive on a mid-tier mobile (Moto G4, 3G fast)1.5 snot yet measured
Lighthouse "Best Practices" on a sample storefront≥ 95100

Why these numbers

  • 100 KB raw — at gzip ratio ~25-30%, that's ~30 KB on the wire; under the 50 KB tipping point where adding our script noticeably shifts Core Web Vitals on a typical Shopify product page.
  • 15 KB raw CSS — Shadow DOM stylesheet, no Tailwind reset bleed, no font files. If this grows past 15 KB the most likely cause is theme bloat that should be Shadow-scoped, not stuffed in here.

Tracked regressions

When a phase pushes a number up, log it here so we can spot drift:

PhaseComponentDeltaReason
1Awidget.js+5 KBGenvorisStyleDetector + CSS variable wiring
1C/E/Gwidget.js+10 KBState machine, focus trap, RTL, 2 new locales
1Hwidget.js+1 KBVersion exposure + product detection polish

Backend (Python upstream, python-backend/backend/*)

MetricBudget
/status p95 latency100 ms
/tryon p95 latency60 s (per-image generation is the bottleneck — UX masks this with progress)
Per-instance memory ceiling1 GB
Cold start5 s

These are not yet enforced in CI. Phase 9 follow-up: wire pytest-benchmark against /status and fail CI on > 100 ms p95 against the local SQLite fixture.

Per-shop / per-IP rate limits

EndpointLimit
Shopify app proxy (any path)20 req/min/shop (sticky on subdomain)
WordPress proxy (any path)30 req/min/IP
Public widget loader60 req/min/IP
Upstream /tryon10 req/min/account

Rate-limit breaches return HTTP 429 with Retry-After. The widget shows a non-blocking toast and re-enables the trigger after the header value elapses.

Bundle-size enforcement

The simplest pre-merge check is a du-style assertion. Add to your release script (an example for the widget):

size=$(wc -c < widget.js)
if [ "$size" -gt 102400 ]; then
echo "widget.js exceeds 100 KB budget ($size bytes)"
exit 1
fi

A future release will move this into a GitHub Action.

What is intentionally NOT a budget

  • Total page weight — that's the host theme's concern; we don't load fonts, third-party CSS, or images on init.
  • Network request count on init — we do at most widget.js (cached), /status (cached), and the storefront's own product images. No analytics beacon, no font fetch, no third-party SDK.

Reviewing this doc

Every major release: re-measure the numbers in the "current" column, update the deltas table, and bump any budget that has changed.