Shopify Plus Optimization: Performance and Conversion Best Practices
Shopify Plus powers some of the world's largest e-commerce brands. This guide covers advanced optimization strategies to maximize performance, conversions, and the overall shopping experience.
Why Does Shopify Plus Performance Matter?
Performance directly impacts revenue:
How Do You Optimize Theme Performance?
Liquid Template Optimization
{% comment %} AVOID: Multiple separate queries {% endcomment %}
{% for product in collection.products %}
{% assign variant = product.variants.first %}
{% assign image = product.featured_image %}
{% endfor %}
{% comment %} BETTER: Paginate and limit data {% endcomment %}
{% paginate collection.products by 20 %}
{% for product in collection.products %}
<article data-product-id="{{ product.id }}">
<img
src="{{ product.featured_image | image_url: width: 400 }}"
srcset="
{{ product.featured_image | image_url: width: 200 }} 200w,
{{ product.featured_image | image_url: width: 400 }} 400w,
{{ product.featured_image | image_url: width: 600 }} 600w
"
sizes="(max-width: 768px) 100vw, 400px"
loading="lazy"
alt="{{ product.title | escape }}"
>
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</article>
{% endfor %}
{% endpaginate %}
Critical CSS Inlining
{% comment %} theme.liquid - inline critical CSS {% endcomment %}
<head>
<style>
/ Critical above-the-fold styles /
:root {
--color-primary: #000;
--color-background: #fff;
}
header { display: flex; align-items: center; }
.hero { min-height: 60vh; }
</style>
{% comment %} Load non-critical CSS asynchronously {% endcomment %}
<link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ 'theme.css' | asset_url }}"></noscript>
</head>
JavaScript Optimization
// Defer non-critical JavaScript
document.addEventListener('DOMContentLoaded', () => {
// Load analytics after page is interactive
setTimeout(() => {
loadAnalytics();
}, 2000);
});
// Use Intersection Observer for lazy loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
if (element.dataset.src) {
element.src = element.dataset.src;
observer.unobserve(element);
}
}
});
}, { rootMargin: '100px' });
document.querySelectorAll('[data-lazy]').forEach(el => observer.observe(el));
How Do You Implement Shopify Hydrogen?
Project Setup
npm create @shopify/hydrogen@latest -- --template demo-store
cd my-hydrogen-store
npm run devCustom Storefront Components
// app/components/ProductCard.tsx
import { Image, Money } from '@shopify/hydrogen';
import { Link } from '@remix-run/react';
export function ProductCard({ product }: { product: ProductCardFragment }) {
const firstVariant = product.variants.nodes[0];
return (
<Link
to={/products/${product.handle}}
className="group"
>
<div className="aspect-square overflow-hidden rounded-lg">
{product.featuredImage && (
<Image
data={product.featuredImage}
className="h-full w-full object-cover transition-transform group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 25vw"
/>
)}
</div>
<div className="mt-4">
<h3 className="text-lg font-medium">{product.title}</h3>
<Money
data={firstVariant.price}
className="text-gray-600"
/>
</div>
</Link>
);
}Server-Side Data Fetching
// app/routes/collections.$handle.tsx
import { json, type LoaderFunctionArgs } from '@shopify/remix-oxygen';
import { useLoaderData } from '@remix-run/react';
import { getPaginationVariables } from '@shopify/hydrogen';
export async function loader({ params, request, context }: LoaderFunctionArgs) {
const { handle } = params;
const { storefront } = context;
const paginationVariables = getPaginationVariables(request, { pageBy: 24 });
const { collection } = await storefront.query(COLLECTION_QUERY, {
variables: { handle, ...paginationVariables },
});
if (!collection) {
throw new Response('Collection not found', { status: 404 });
}
return json({ collection });
}
export default function Collection() {
const { collection } = useLoaderData<typeof loader>();
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">{collection.title}</h1>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{collection.products.nodes.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
</div>
);
}What Conversion Rate Optimizations Work Best?
Cart Drawer Implementation
class CartDrawer extends HTMLElement {
constructor() {
super();
this.bindEvents();
}
bindEvents() {
document.addEventListener('cart:add', this.onCartAdd.bind(this));
document.addEventListener('cart:update', this.onCartUpdate.bind(this));
this.querySelector('[data-close]').addEventListener('click', () => {
this.close();
});
}
async onCartAdd(event) {
const { id, quantity } = event.detail;
const response = await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: [{ id, quantity }] })
});
if (response.ok) {
await this.refresh();
this.open();
}
}
async refresh() {
const response = await fetch('/cart.js');
const cart = await response.json();
this.render(cart);
}
open() {
this.setAttribute('open', '');
document.body.style.overflow = 'hidden';
}
close() {
this.removeAttribute('open');
document.body.style.overflow = '';
}
}
customElements.define('cart-drawer', CartDrawer);
Quick Add to Cart
<form
action="/cart/add"
method="post"
class="quick-add-form"
data-product-id="{{ product.id }}"
>
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<input type="hidden" name="quantity" value="1">
<button
type="submit"
class="quick-add-button"
{% unless product.available %}disabled{% endunless %}
>
{% if product.available %}
Add to Cart
{% else %}
Sold Out
{% endif %}
</button>
</form>
<script type="module">
document.querySelectorAll('.quick-add-form').forEach(form => {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const button = form.querySelector('button');
button.disabled = true;
button.textContent = 'Adding...';
const formData = new FormData(form);
await fetch('/cart/add.js', {
method: 'POST',
body: formData
});
button.textContent = 'Added!';
document.dispatchEvent(new CustomEvent('cart:refresh'));
setTimeout(() => {
button.disabled = false;
button.textContent = 'Add to Cart';
}, 2000);
});
});
</script>
How Do You Monitor Shopify Performance?
Core Web Vitals Tracking
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics({ name, delta, id }) {
gtag('event', name, {
event_category: 'Web Vitals',
event_label: id,
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
non_interaction: true,
});
}
onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
Optimizing Shopify Plus requires attention to both technical performance and user experience. By implementing these strategies, you can create a fast, conversion-optimized store that delivers results.