Shopify Hydrogen Journal
Optimizing Core Web Vitals in Shopify Hydrogen Storefronts
April 18, 2026
## Why Core Web Vitals matter for Hydrogen stores
Google uses Core Web Vitals — LCP, INP, and CLS — as ranking signals. For an ecommerce storefront, poor scores directly translate to lower organic traffic and worse conversion rates. Hydrogen gives you the tools to nail all three, but you need to use them intentionally.
This guide covers the specific techniques that move the needle on real Hydrogen projects.
## LCP (Largest Contentful Paint) — target < 2.5s
For most storefronts, LCP is the hero product image on the product page or the banner image on the homepage. Fix it here:
### 1. Use the Hydrogen Image component with fetchpriority
```tsx
import {Image} from '@shopify/hydrogen';
export function HeroImage({image}: {image: ProductImage}) {
return (
);
}
```
Avoid lazy-loading anything above the fold. The `fetchpriority="high"` hint is processed before layout, so it fires earlier than a preload link.
### 2. Preload your LCP image in the loader
```typescript
export async function loader({params, context}: LoaderFunctionArgs) {
const {product} = await context.storefront.query(PRODUCT_QUERY, {
variables: {handle: params.handle},
});
// Preload the hero image via Link header
const heroImage = product.featuredImage;
return json(
{product},
{
headers: {
Link: `<${heroImage?.url}&width=1200>; rel=preload; as=image`,
},
}
);
}
```
This pushes a `Link: preload` header from the edge, so Oxygen starts fetching the image before the browser even parses your HTML.
## INP (Interaction to Next Paint) — target < 200ms
INP measures responsiveness. The most common INP killer in Hydrogen storefronts is the **Add to Cart** button.
### Use optimistic UI for cart mutations
```tsx
import {useFetcher} from '@remix-run/react';
export function AddToCartButton({variantId}: {variantId: string}) {
const fetcher = useFetcher();
const isAdding = fetcher.state !== 'idle';
return (
);
}
```
The `useFetcher` pattern gives instant visual feedback without blocking the UI thread.
## CLS (Cumulative Layout Shift) — target < 0.1
CLS measures how much the page jumps around as it loads. Common causes in Hydrogen:
### 1. Always set explicit dimensions on images
```tsx
// Bad — causes layout shift
// Good — aspect ratio reserved
```
### 2. Reserve space for deferred content
If you use `defer` for non-critical data, always provide a skeleton:
```tsx
import {Suspense} from 'react';
import {Await} from '@remix-run/react';
}>
{(data) => }
```
The skeleton must have the same height as the real content, otherwise you get a shift when it loads.
## Sub-request caching: the Hydrogen secret weapon
Hydrogen's cache API lets you cache Storefront API responses at the edge. This reduces both LCP and TTFB significantly:
```typescript
export async function loader({context}: LoaderFunctionArgs) {
const {storefront, cache} = context;
const data = await storefront.query(COLLECTION_QUERY, {
variables: {handle: 'all'},
cache: storefront.CacheShort(), // Cache for 1 minute
// storefront.CacheLong() // Cache for 1 hour (for rarely changing content)
// storefront.CacheNone() // No cache (for cart, account)
});
return json(data);
}
```
For product listings that change infrequently, `CacheLong()` can cut your TTFB by 300-500ms.
## Practical checklist
Before launching a Hydrogen storefront, run through this:
- [ ] Hero images use `fetchpriority="high"` and `loading="eager"`
- [ ] LCP image preloaded via Link header in the loader
- [ ] All images have explicit width and height
- [ ] Deferred content has same-height skeleton placeholders
- [ ] Add to Cart uses `useFetcher` for optimistic feedback
- [ ] Collection and product queries use `CacheShort()` or `CacheLong()`
- [ ] Third-party scripts loaded with `defer` or in a `useEffect`
- [ ] Fonts preloaded in root.tsx
Run PageSpeed Insights on your deployed Oxygen URL (not localhost) to measure real scores. Oxygen's edge network changes the numbers significantly compared to local dev.