Most Shopify speed optimization guides recommend installing apps to fix speed problems. The irony is that apps cause most speed problems in the first place. A pure Shopify optimization approach - using native platform features, custom Liquid code, and browser-native capabilities - can achieve scores of 75 to 90 on mobile without a single optimization app installed. The method is more durable, faster, and cheaper to maintain than any app-dependent approach.
Why Avoiding Apps Is the Right Starting Point
Every Shopify performance guide eventually reaches the same uncomfortable conclusion: the fastest Shopify stores have the fewest apps. Not because apps are inherently bad, but because each one adds a layer of JavaScript, CSS, and network requests that compounds across every page load.
The app-free optimization approach starts from a different premise: before installing anything, exhaust what Shopify already provides natively. The platform has grown significantly. Features that required third-party apps two or three years ago now exist natively in Shopify itself. Native code performs better than third-party code in almost every case, because native code runs in the same environment as the platform, uses the same APIs without wrappers, and does not require external network requests to initialize.
What Native Shopify Already Does for You
Before writing a single line of custom code, understand what Shopify handles without any intervention.
Shopify automatically serves all uploaded images through a global CDN. The image_url Liquid filter with a width parameter generates the correctly-sized WebP image on Shopify's CDN and serves it from the nearest edge server. No image optimization app required.
{{ product.featured_image | image_url: width: 800, format: 'webp' }}
CSS and JavaScript files stored in your theme's assets folder are automatically minified by Shopify when served to visitors. The minification happens at the CDN level. No minification plugin needed.
Shopify's servers use HTTP/2 and Brotli compression by default. All assets are compressed before transfer. Response headers are multiplexed. These infrastructure-level optimizations apply to every Shopify store automatically.
Shopify provides a product recommendations API at /recommendations/products.json. Pass a product ID and limit, receive recommendations based on purchase and browsing data. No recommendation app required for most use cases.
Shopify's cart API is available natively via JavaScript. A custom cart drawer requires no cart app - it requires familiarity with fetch('/cart/add.js') and the Cart API endpoints. Shopify's own Dawn theme demonstrates this completely.
Back-in-stock notifications are available natively through Shopify's storefront for most plan levels. Gift cards and discount codes are native to Shopify checkout. Check your Online Store settings before installing third-party apps for any of these.
Native Shopify Optimization Techniques
With the platform's built-in capabilities understood, here are the optimization techniques that require no external dependencies.
Shopify's Dawn theme is open source and built to the highest performance standards the platform supports. Study how it handles variant switching without jQuery, structures JavaScript into small page-specific modules, implements lazy loading throughout the template, and manages font loading and CLS prevention. Dawn's source code on GitHub is the single best reference for performant Shopify theme development.
Native browser lazy loading requires no JavaScript. Add
loading="lazy" to every image below the fold. Zero JavaScript. Zero dependencies. Supported by every modern browser.{% for product in collection.products %}
<img
src="{{ product.featured_image | image_url: width: 400, format: 'webp' }}"
loading="{{ forloop.first ? 'eager' : 'lazy' }}"
width="{{ product.featured_image.width }}"
height="{{ product.featured_image.height }}"
alt="{{ product.featured_image.alt | escape }}"
>
{% endfor %}
Shopify's section schema supports loading JavaScript and CSS only when a section is used on a page. A product page with no testimonials section loads no testimonials code. No optimization app achieves this level of precision - it requires understanding Shopify's section architecture.
{% schema %}
{
"name": "Testimonials",
"stylesheet": "section-testimonials.css",
"javascript": "section-testimonials.js"
}
{% endschema %}
The cart drawer is one of the most commonly app-powered features on Shopify stores. The core implementation using Shopify's Cart API natively is lighter, faster, and more customizable than any cart app. It makes two fetch requests and updates your UI directly - no external script download, no external network connection, no third-party JavaScript.
async function addToCart(variantId, quantity = 1) {
const response = await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: variantId, quantity })
});
if (response.ok) {
const cartResponse = await fetch('/cart.js');
const cart = await cartResponse.json();
updateCartUI(cart);
openCartDrawer();
}
}
function updateCartUI(cart) {
document.querySelector('.cart-count').textContent = cart.item_count;
// Render cart items from cart.items array
}
A custom popup implementation requires no app. Combined with a Liquid snippet for the popup HTML and a small CSS block for the styles, 30 lines of code replace a popup app with zero external dependencies that load instantly.
// Show popup after 8 seconds, only once per session
function initEmailPopup() {
if (sessionStorage.getItem('popup_shown')) return;
setTimeout(function() {
document.querySelector('.email-popup').classList.add('visible');
sessionStorage.setItem('popup_shown', 'true');
}, 8000);
}
Recently viewed products use localStorage to persist product data between pages. This replaces a recently-viewed-products app with lightweight, dependency-free JavaScript that runs entirely in the browser.
const RecentlyViewed = {
getProducts() {
return JSON.parse(localStorage.getItem('recently_viewed') || '[]');
},
addProduct(productData) {
const products = this.getProducts();
const filtered = products.filter(p => p.id !== productData.id);
filtered.unshift(productData);
localStorage.setItem('recently_viewed', JSON.stringify(filtered.slice(0, 8)));
}
};
// On product pages, store current product
RecentlyViewed.addProduct({
id: {{ product.id }},
title: {{ product.title | json }},
url: '{{ product.url }}',
image: '{{ product.featured_image | image_url: width: 300, format: "webp" }}',
price: {{ product.price }}
});
Custom Coding Strategies for Speed
jQuery is a 30KB library that modern JavaScript replaced. Every DOM operation jQuery performs can be done natively with less code and no library download. Modern browsers support querySelector, classList, fetch, Promise, and async/await without any library.
Many interactive behaviors can be implemented in CSS without JavaScript: FAQ accordions using <details> and <summary> elements, tab interfaces using CSS :target, image galleries using CSS scroll snap, sticky elements using position: sticky, and hover dropdowns using CSS :hover.
Any behavior that needs to trigger based on scroll position should use Intersection Observer, not scroll event listeners. Intersection Observer runs off the main thread and is built into the browser. This replaces JavaScript scroll libraries like AOS and ScrollReveal with a few lines of native browser code.
// Animate elements into view as they scroll - no library needed
const animateObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
animateObserver.unobserve(entry.target);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.animate-on-scroll').forEach(el => {
animateObserver.observe(el);
});
type="module" on script tags gives you automatic deferred loading (modules are always deferred), strict mode by default, and proper code isolation. Module scripts load asynchronously and execute after the DOM is ready. No defer attribute needed. No dependency management library required.Reducing Dependencies Throughout Your Shopify Store
Dependency reduction is a systematic practice, not a one-time action. Create a dependency register listing every library, framework, and external resource your theme uses, then evaluate each against native alternatives.
| Dependency | Size | Purpose | Native Alternative |
|---|---|---|---|
| jQuery | 30KB | DOM manipulation | Vanilla JS |
| Swiper.js | 65KB | Image carousel | CSS scroll snap |
| Font Awesome | 72KB | Icons | Inline SVG |
| Lodash | 25KB | Utility functions | Native array methods |
| AOS | 15KB | Scroll animations | Intersection Observer |
Apps vs Custom Code: An Honest Comparison
- Functionality that loads on every page (cart drawer, popups, recently viewed)
- Long-term maintenance - code you own cannot change pricing or shut down
- Features with straightforward requirements (popup timers, FAQ accordions, countdown timers)
- Any feature where the app wraps simple functionality in complex infrastructure
- Complex integrations with external platforms (email marketing, accounting, shipping carriers)
- Functionality requiring ongoing data management (review moderation, loyalty points, subscriptions)
- Rapidly evolving features (payment processing, tax computation, shipping rate calculation)
Performance Benefits of the App-Free Approach
Fewer External Network Connections
Each app domain requires a separate network connection. Ten apps mean at least ten external DNS lookups, TCP handshakes, and TLS negotiations. On mobile, each connection adds 50 to 200ms of overhead. An app-free store makes connections only to Shopify's CDN and whatever minimal external services you genuinely need.
Reduced JavaScript Execution Time
App JavaScript runs in the same browser environment as your theme code, competing for the same main thread. A custom cart drawer initialized in 15ms versus an app cart drawer initializing in 120ms across 1,000 daily visitors is 1.75 minutes of combined visitor wait time saved every day.
No App Update Regression Risk
Apps update their code on their own schedule. A well-performing store can regress overnight when an app releases an update that adds new JavaScript weight without notice. Custom code changes only when you change it.
Lower Cumulative Layout Shift
App-injected content frequently causes CLS because apps cannot predict the exact layout of your theme. Native Liquid-rendered content is part of the same render pass as the rest of your page, reducing unexpected layout shifts.
Cost vs Performance: The True Calculation
A typical Shopify store with 10 to 15 apps pays 200 to 600 dollars per month in app subscription fees. Over 12 months, that is 2,400 to 7,200 dollars. The hidden cost is performance: a store scoring 38 on mobile PageSpeed versus 72 is not just a score difference - it is conversion rate. Google's research shows each second of LCP above 2.5 seconds reduces mobile conversion probability significantly.
Developer time is the primary cost. A skilled Shopify developer replacing a cart app, a popup app, and a recently-viewed-products app with custom code might spend 8 to 15 hours - a one-time cost of 800 to 2,000 dollars. After the initial implementation, maintenance costs are low: an hour or two per quarter. Custom code has no ongoing subscription cost.
Testing Pure Shopify Optimization Improvements
Best Practices for the App-Free Approach
Default to Native First
Evaluation order: (1) does Shopify already support this natively, (2) can this be built in under 100 lines of code, (3) does an app exist that covers exactly this need without excess features, (4) is the app's performance cost justified by the business value.
Keep a Performance Budget
Set a target: no more than 5 external script domains, total JavaScript below 300KB, and mobile PageSpeed above 65. Every new feature proposal gets evaluated against this budget. If a proposed feature would break the budget, alternatives must be explored first.
Review Dawn Theme Updates Quarterly
Shopify regularly improves Dawn's performance characteristics and adds new native features. Reviewing Dawn updates quarterly reveals new native capabilities that may allow you to remove custom code you wrote as a workaround for missing platform features.
Document Every Custom Implementation
Write a brief document for each custom feature explaining what it does, how it is implemented, which theme files it touches, and what to check if something breaks. Always work on a duplicate theme when making significant changes and keep the previous working version available as a fallback.
Summary
The fastest Shopify stores are not the ones with the best optimisation apps. They are the ones that understood what the platform already provides and built carefully on top of it rather than layering dependency upon dependency.
<details> element, ES modules - these are all performance-first capabilities available to every Shopify store with no subscription fee and no external JavaScript.Use native Shopify features first. Write custom code for what native features do not cover. Install apps only for functionality that genuinely requires their backend infrastructure. Test everything, document what you build, and maintain it like the performance investment it is.
The stores that achieve this discipline do not just score better in PageSpeed. They convert better, rank higher, and spend less every month than stores that treated every problem as an app installation away from being solved. For stores that need automated image optimization and performance monitoring without adding general-purpose app bloat, Ecom: Page Speed Expert is purpose-built around Shopify's architecture to keep your store lean rather than adding to it.