.dev
Storefront templates

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

ConcernOwner
Isolated SSR, worker limits and public result cachingHelium runtime
Custom-code precompile (compiledCode)Lithium runtime
Document TTFB, API latency and edge cachingRuntime, services and hosting
Hydration strategy and cost of custom browser effectsTheme author, with runtime scheduling
Images, fonts, contrast, tap targets and layout stabilityTheme 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–900 with srcSet / sizes — never a default w=1600 or w=1200 URL, and especially not one marked fetchPriority="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: 0 until onLoad) 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 / #666 for muted copy — not #888, not opacity: 0.7, not a 60% color-mix with transparency.
  • Tap targets — footer menus and legal links at 48×48 CSS px minimum; no tightly-stacked padding: 5px 0 link lists.
  • Text size — interactive labels at 12px or more (badges may be 12px, never 10px).
  • Link names — skip aria-label on RouterNav / links whose visible text already names the control (triggers the label-content-name-mismatch audit).
  • Decorative numerals — even aria-hidden display digits should hold 3:1 contrast; Lighthouse sometimes audits them anyway.
  • Account link — prefetch={false} on any /account link (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 fail

It greps your source (.tsx, .jsx, .ts, .js, .css) for the patterns above — no Lighthouse, no browser, instant feedback. What it flags:

Rule idLevelTrigger
hero-widtherrorUnsplash URL with w=1200+ and no mobile rewrite (heroSrc helper / w=640–900) in the same file
google-cairoerrorRuntime fonts.googleapis.com Cairo injection
contrast-hexerrorKnown-failing muted hexes (#888, #8DA0B5, #74777f)
opacity-onloadwarnopacity: 0 until onLoad fade-in patterns
account-prefetchwarn/account link without prefetch={false}
thumb-resizewarnProduct 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.

On this page