Performance
LCP and image rules for themes, the check:perf static scanner, accessibility budgets, and PageSpeed verification.
Measure the storefront origin and the shopper's browser separately. Origin throughput and RSS tell you about rendering and memory; Lighthouse and field Core Web Vitals tell you about loading, layout stability and responsiveness. A faster origin does not guarantee a particular Lighthouse score.
Theme goals remain mobile lab Performance ≥ 80 and Accessibility / Best Practices / SEO at 100. Treat these as release targets to verify, not scores guaranteed by the runtime.
Who owns what
| Concern | Owner |
|---|---|
| Isolated SSR, worker limits and public result caching | Helium runtime |
Custom-code precompile (compiledCode) | Lithium runtime |
| Document TTFB, API latency and edge caching | Runtime, services and hosting |
| Hydration strategy and cost of custom browser effects | Theme author, with runtime scheduling |
| Images, fonts, contrast, tap targets and layout stability | Theme author |
For Helium, read the hydration guide. Custom components
hydrate by default. Static CSS and simple footer blocks can explicitly use never after a
behavior review; keep shopping controls interactive.
Helium origin benchmark snapshot
The 2026-10-16 local matched comparison used 1,000 synthetic stores, then warm mixed-page load. At four connections, successful requests/s rose from 79.94 to 1,303.4 and p99 fell from 82 ms to 9 ms. At 48 connections, HTTP failures fell from 94,168 to zero.
A separate 8,000-store, 16,000-request sweep returned zero render/HTTP errors. Late RSS median was 137.3 MiB, with 2.0 MiB second-half median growth. Highest sampled RSS was 155.7 MiB during the sweep and 160.9 MiB during follow-up load. 150 MiB is a median gate, not a maximum RSS cap; retain the 512 MiB container allowance.
These tests ran on macOS arm64 / Node v25.3.0 with eight available logical CPUs, stubbed services and images/orders/compression disabled. They include short-lived result caching and a smaller V8 heap profile. Repeat them under the Linux deployment's resource limits before capacity planning. See the full methodology and benchmark data.
Image rules
Hero / LCP
- Serve the hero at
w=640–900withsrcSet/sizes— never a defaultw=1600orw=1200URL, and especially not one markedfetchPriority="high". - Exactly one
fetchPriority="high"per page — usually the hero. Not the hero and the first product card. - The LCP candidate must be visible immediately (
opacity: 1). No fade-in-on-load (opacity: 0untilonLoad) on the hero or the first row of cards. - Skip scroll-reveal animations on above-the-fold content — treat the first row
(
order <= 2) as already revealed.
Product thumbnails
Map every card image through a small cardImageUrl helper before paint, downsizing to
roughly 320px: Unsplash w=320, Feeef storage ?width=320, Shopify CDN ?width=320. Never
paint the full-size w=900 API URLs a list endpoint returns.
Seeded home products
The storefront may inject the home page's first product rows server-side via
useSlotContext('homeProducts'). Use the seed and do not refetch-and-replace it — the
swap causes layout shift and wastes the LCP work:
const seeded = useSlotContext('homeProducts');
const initial = Array.isArray(seeded) ? seeded : [];
const [products, setProducts] = React.useState(mapProducts(initial));
const [loading, setLoading] = React.useState(initial.length === 0);
React.useEffect(() => {
if (initial.length > 0) return; // keep the paint stable — do not replace seeded rows
// fetch only when the seed is empty…
}, [/* … */]);Layout stability
Give every product/story/grid image explicit width / height attributes or a CSS
aspect-ratio — unsized images are the classic CLS source.
Fonts
Use the template's props.font (e.g. "cairo") and the runtime's supported font loading.
Lithium uses next/font; Helium supplies its own font assets.
Never inject a runtime Google Fonts stylesheet (fonts.googleapis.com) from theme code; you
would double-load the font and add a render-blocking request.
Accessibility budget
These are the audits that most often cost themes their 100:
- Contrast — body and muted text at 4.5:1 minimum (3:1 for large text). On light
backgrounds use solid
#555/#666for muted copy — not#888, notopacity: 0.7, not a 60%color-mixwith transparency. - Tap targets — footer menus and legal links at 48×48 CSS px minimum; no
tightly-stacked
padding: 5px 0link lists. - Text size — interactive labels at 12px or more (badges may be 12px, never 10px).
- Link names — skip
aria-labelonRouterNav/ links whose visible text already names the control (triggers the label-content-name-mismatch audit). - Decorative numerals — even
aria-hiddendisplay digits should hold 3:1 contrast; Lighthouse sometimes audits them anyway. - Account link —
prefetch={false}on any/accountlink (or hide it) until that route exists; prefetching a missing route logs a 404 that costs Best Practices points.
The check:perf scanner
Every theme package scaffolded by the kit ships a static scanner:
npm run check:perf # scan pages/ shared/ library/
npm run check:perf -- --strict # warnings also failIt greps your source (.tsx, .jsx, .ts, .js, .css) for the patterns above — no
Lighthouse, no browser, instant feedback. What it flags:
| Rule id | Level | Trigger |
|---|---|---|
hero-width | error | Unsplash URL with w=1200+ and no mobile rewrite (heroSrc helper / w=640–900) in the same file |
google-cairo | error | Runtime fonts.googleapis.com Cairo injection |
contrast-hex | error | Known-failing muted hexes (#888, #8DA0B5, #74777f) |
opacity-onload | warn | opacity: 0 until onLoad fade-in patterns |
account-prefetch | warn | /account link without prefetch={false} |
thumb-resize | warn | Product list paints photoUrl with no visible w=320 / cardImageUrl mapping |
Errors exit non-zero; warnings pass unless --strict. The scan is heuristic — a clean run
is necessary, not sufficient.
Lab Lighthouse
For a real score, run Lighthouse against a live demo store (requires Chrome; the script lives in the storefront workspace of the monorepo):
# from storefront/
npm run perf:lh -- https://your-store.feeef.store/
PERF_CATEGORIES=all npm run perf:lh -- https://your-store.feeef.store/Or simply point PageSpeed Insights at your published demo store — same lab conditions, zero setup.
Workflow after edits
Run npm run check:perf in the theme package and fix any errors.
npm run build, then hard-refresh the draft preview (npm run dev).
After publishing, re-run lab Lighthouse (or PageSpeed Insights) on the demo store.
Related
Marketing pixels
How pixel events fire from themes — funnel events in the browser, order conversions from the server, GA4, the FeeefPixels helper, dynamic Lead/Purchase objectives, and testing.
Authoring conventions
The design-system.md contract, currency symbols, GFM product bodies, the Feeef footer credit, and naming rules every theme must follow.