Performance and Core Web Vitals
Hyprism is built to clear Shopify Theme Store performance gates with margin. This chapter covers the optimizations baked in, what targets to expect, and how to verify performance on your store.
14.1 Targets
Section titled “14.1 Targets”Hyprism aims for these performance targets on every template:
| Metric | Mobile target | Desktop target | Theme Store gate |
|---|---|---|---|
| Lighthouse Performance | ≥85 (median 92) | ≥95 (median 99–100) | ≥75 mobile |
| Largest Contentful Paint (LCP) | <2.5s | <1.5s | <2.5s |
| Cumulative Layout Shift (CLS) | <0.1 | <0.1 | <0.1 |
| Total Blocking Time (TBT) | <300ms | <100ms | <300ms |
| Interaction to Next Paint (INP) | <200ms | <200ms | <200ms |
The median after 4 iterative Lighthouse runs was:
- Mobile: 92 (Performance), 96–100 (A11y), 100 (SEO) — only Cart shows SEO=69 because
/cartis intentionally blocked from indexing via robots.txt. - Desktop: 99–100 (Performance), 96–100 (A11y), 100 (SEO).
⚠️ Read those numbers with their basis. They were measured on a development store carrying apparel demo content — a product page of roughly nine blocks. A technical store following recipe 22.2 puts twenty on the same page, most of them reading metafields. The blocks are server-rendered Liquid with no additional requests, so the cost is markup rather than JavaScript, but the figures above are not a promise for a page three times the size. Measure your own build — §14.5 says how.
14.2 What’s optimized
Section titled “14.2 What’s optimized”Critical CSS
Section titled “Critical CSS”The theme’s critical above-the-fold CSS (the styles needed before any section renders) is in assets/critical.css. It’s loaded synchronously in <head> with <link rel="preload"> so the browser fetches it early.
Non-critical CSS lives in {% stylesheet %} blocks inside individual section/block Liquid files — Shopify bundles these into per-section CSS files that load only when that section is on the page.
Preconnect and preload
Section titled “Preconnect and preload”In layout/theme.liquid <head>:
{% raw %}{{ 'critical.css' | asset_url | stylesheet_tag: preload: true }}
{{ settings.type_body_font | font_url | preload_tag: as: 'font', type: 'font/woff2', crossorigin: 'anonymous', fetchpriority: 'high' }}{{ settings.type_heading_font | font_url | preload_tag: as: 'font', type: 'font/woff2', crossorigin: 'anonymous', fetchpriority: 'high' }}{% endraw %}```
All four font roles are preloaded, each at `fetchpriority: high`.
⛔⛔ **There are no preconnects at all, and that is deliberate.** Shopify serves theme assets *and* fonts from the store's own domain, so a preconnect to `cdn.shopify.com` or `fonts.shopifycdn.com` is an unused connection — Lighthouse flags it, and removing it freed roughly 1500 ms of critical-path latency on the home page. Only preconnect to an origin you genuinely fetch from cross-origin.
⭐ **Why the heading font is `high` and not `low`.** It pairs with `font-display: optional`, which only uses a webfont that arrives inside the browser's ~100 ms block window and otherwise keeps the fallback for that page view. That is what buys a shift-free first paint — but it also means a late font is a font the visitor never gets. The headline is the first thing measured for layout, so de-prioritising it worked against both goals at once.
### Image optimization
- **All images use `image_url` filter** for the Shopify CDN, which auto-converts to AVIF/WebP based on browser support.- **Section-position-aware fetchpriority**: hero/first-section images get `fetchpriority="high"` for LCP; below-fold images get `loading="lazy"` for delayed loading.- **`decoding="async"`** on every `<img>` — browser decodes images off the main thread, so image rendering doesn't block scroll responsiveness.- **Subgrid card alignment** keeps all product cards in a grid identical-height (no layout shift as images load at different speeds).
### Passive event listeners
All scroll, touch, and wheel listeners use `{ passive: true }`:
```javascriptwindow.addEventListener('scroll', handler, { passive: true });section.addEventListener('touchstart', handler, { passive: true });This tells the browser “I won’t preventDefault” — the browser can start scrolling immediately without waiting for the JS to run. Massively improves INP.
Deferred JS
Section titled “Deferred JS”Every theme JS script uses defer:
<script src="hyprism.js" defer></script><script src="hyprism-features.js" defer></script>JS doesn’t block HTML parsing. It executes after DOMContentLoaded.
IntersectionObserver for lazy work
Section titled “IntersectionObserver for lazy work”Instead of scroll listeners that fire on every scroll event, Hyprism uses IntersectionObservers for:
- Sticky Product Bar (watches the main ATC)
- Mobile Sticky Cart Bar (watches the in-flow summary)
- Off-screen card visibility-hiding in slider carousels
- Recently-viewed tracking (when a product page enters viewport, the product is added to localStorage)
IntersectionObservers fire only when an element crosses a threshold — orders of magnitude cheaper than scroll listeners.
Animation considerations
Section titled “Animation considerations”- Transform-only animations (translate, scale, rotate) — the browser composites these on GPU without main-thread cost
will-changeonly when transitioning — not set permanently (which would keep elements on a GPU layer always)backdrop-filteronly on explicit glass elements — not on overlay-wrappers (which would force the entire page through GPU compositor)
14.3 What’s intentionally NOT optimized
Section titled “14.3 What’s intentionally NOT optimized”A few things that could be optimized but aren’t, because the trade-offs aren’t worth it:
Preload-link for product / article featured images (REVERTED)
Section titled “Preload-link for product / article featured images (REVERTED)”Tried in v0.7-Round-2: <link rel="preload" as="image"> for the product page’s main image. On throttled-mobile (Lighthouse’s mobile profile = 4× CPU + Slow-4G), the preload competed with critical-path resources for bandwidth, and FCP regressed +0.6s. Net-negative — reverted.
Lesson: preload helps only for late-discovered resources (JS-driven images, dynamically-imported fonts). For in-the-HTML images, the browser’s default discovery is already optimal.
Logo fetchpriority="low" (REVERTED)
Section titled “Logo fetchpriority="low" (REVERTED)”Tried in v0.7-Round-2: deprioritize the header logo to free bandwidth for the hero. On throttled-mobile, this delayed FCP because the logo is part of the first paint. Browser-default heuristics (auto-prio for above-fold images) were already correct. Reverted.
Lesson: don’t speculatively deprioritize above-fold resources — the browser knows better.
14.4 Lighthouse Best Practices = 77
Section titled “14.4 Lighthouse Best Practices = 77”This score is constant across all 13 Hyprism templates because the failing items are Shopify-platform-level, not theme-fixable:
| Failed Item | Cause | Fixable? |
|---|---|---|
CSP script-src and object-src missing | Shopify edge sets Content-Security-Policy header that Hyprism can’t override | No — Shopify-level |
| HSTS max-age too low | Shopify edge sets HSTS header | No |
| No COOP (Cross-Origin-Opener-Policy) | Shopify edge | No |
| No Trusted Types | Shopify edge | No |
| Third-party cookies (shop.app) | Shopify ecosystem | No |
Dawn and Horizon score 77 too — reviewers are familiar with this plateau and don’t reject themes for it.
14.5 Verifying performance on your store
Section titled “14.5 Verifying performance on your store”Run Lighthouse
Section titled “Run Lighthouse”In Chrome / Edge:
- Open the page you want to test.
- DevTools (F12) → Lighthouse tab.
- Pick “Mobile” + “Performance, Accessibility, Best Practices, SEO” + “Slow 4G + 4× CPU”.
- Click “Analyze page load”.
Run 3–5 times and take the median — Lighthouse Mobile has natural variance (±5–15 points per run, even with no code changes).
Run Shopify Web Performance Dashboard
Section titled “Run Shopify Web Performance Dashboard”Shopify Admin → Online Store → Themes → Customize → Performance tab. Shopify tests every published theme weekly and reports CWV.
Run real-user monitoring
Section titled “Run real-user monitoring”Once your store is live with real traffic, Shopify’s Performance tab shows real-user CWV data (anonymized aggregate). This is more meaningful than Lighthouse because it reflects what real visitors experience.
14.6 Performance tips for content choices
Section titled “14.6 Performance tips for content choices”Things merchants control that affect performance:
| Choice | Impact | Recommendation |
|---|---|---|
| Hero video file size | High | Compress to ≤2MB for autoplay-loops |
| Hero image size | Medium | Upload at 2× display size; let Shopify CDN serve optimized formats |
| Number of sections on home page | Low | Generally fine; Hyprism uses section-scoped CSS so unused sections aren’t loaded |
| Number of apps installed | High | Each app injects JS. Apps often hurt performance more than themes. |
| Custom Liquid section content | Variable | Custom Liquid runs server-side, so it’s free at runtime. JavaScript inside Custom Liquid can be costly. |
14.7 INP (Interaction to Next Paint)
Section titled “14.7 INP (Interaction to Next Paint)”INP is Google’s newer metric (replacing FID in 2024). It measures the longest input-to-paint delay during a page session — basically “how snappy does this page feel?”
Hyprism INP targets <200ms. To achieve this:
- All JS listeners are passive where applicable
- Main-thread tasks are <50ms each
- Animations are GPU-composited (transform / opacity / filter)
- IntersectionObserver replaces scroll listeners
- AJAX cart updates are event-driven, no polling
If your INP is high on a specific page, check:
- Apps with heavy JS — Shopify Apps tab → Performance tab shows per-app impact
- Custom Liquid sections — large
forloops in Liquid run server-side (fine) but JSON output rendered as<script>and parsed in JS can be slow if huge - Marketing pixels — Meta Pixel, TikTok Pixel, etc. can be heavy
Chapter 15 — Localization — multi-language store setup.