Why Your Shopify Store Is Slow—and What to Fix First
Find the real bottleneck, prioritise the right fix, and make every image, script, app, and theme decision work harder for your customers.
A slow Shopify store rarely has one obvious cause. A large banner can delay the first screen, an app can make product options hesitate, and a review widget can push the buying button down after it appears. Each problem needs a different fix.
Shopify provides managed storefront infrastructure and a content delivery network, but your theme, media, and integrations still influence the customer experience. This guide focuses on stores using Shopify's Liquid themes; headless storefronts require additional investigation.[1]
Measure a real shopping journey, identify its bottleneck, change one thing, and retest. Installing another optimisation app before diagnosing the problem can add complexity without improving the experience.
1. Build a baseline you can trust
Start with Shopify's web performance reporting and examine mobile visitors separately. Review your busiest product, collection, and landing pages. Homepage results alone cannot represent shoppers arriving directly from advertisements or product searches.
Use field data to understand actual visitors, then Lighthouse, PageSpeed Insights, or Chrome DevTools to investigate specific causes. Lab tests offer repeatable conditions; field measurements reflect different devices, networks, and journeys. They answer related questions and can disagree without either being broken.[2]
Record the URL, template, device conditions, theme version, active apps, and test date. Repeat the same test several times and compare typical results. Test a clean browser session without extensions, alongside the returning customer experience. Keep consent settings consistent when comparing tracking behaviour.
Include products with many variants, collections with filters, and pages containing reviews or video. Save screenshots and traces, not just a final score. When a campaign changes the visitor mix, compare similar audiences before concluding that a theme change improved or damaged performance.
In PageSpeed Insights, check whether the field report represents the specific URL or the wider origin. A page without enough eligible traffic might not have its own field data. Use the lab trace to investigate that page, while keeping the broader field context visible to the whole delivery team.[3]
2. Separate loading, interaction, and stability
Largest Contentful Paint, or LCP, measures when the largest visible content appears. Interaction to Next Paint, or INP, measures responsiveness. Cumulative Layout Shift, or CLS, measures unexpected movement. Google's good thresholds are LCP within 2.5 seconds, INP at most 200 milliseconds, and CLS at most 0.1, assessed at the 75th percentile.[4]
These are starting points, not automatic diagnoses. A slow server response can delay LCP, while a script can affect several metrics. Follow the evidence before assigning responsibility to an image, app, or theme.
During an interaction recording, identify whether the delay happens before the event handler starts, inside its processing, or before the browser paints the result. This distinction prevents wasted work: compressing an image will not resolve a variant selector blocked by an expensive calculation.
3. Make image loading match visibility
Identify the actual LCP element in your performance trace. If it is a hero or product image, ensure the browser discovers it early. Avoid lazy loading that image or hiding it behind a reveal animation. Consider fetchpriority="high" for the confirmed LCP image, rather than every image.[5]
Generate responsive image markup with Shopify's image_url and image_tag filters. Supply suitable widths and a sizes value that matches the layout. A narrow product card should not download an unnecessarily large original. Preserve width and height attributes so space exists before the image arrives.[6]
Lazy load images genuinely outside the initial viewport. Replace heavy autoplay video with a useful poster and load playback when requested. Inspect mobile and desktop separately because cropping, layout, and the identity of the LCP element can change.
When mobile and desktop need different artwork, consider a picture element with appropriate sources instead of displaying two image elements and hiding one with CSS. Inspect actual network downloads to confirm the browser requests the intended asset. Check visual quality before reducing image dimensions or compression further.[7]
4. Audit apps by their actual cost
List storefront apps, app embeds, custom pixels, and manually inserted scripts. Record what each does and where it is needed. App count alone is misleading: one expensive widget can create more browser work than several lightweight features.
Use the Network and Performance panels to connect requests and execution time to their owners. Investigate duplicate analytics, overlapping review tools, unnecessary popups, and abandoned experiments. Confirm attribution before removing anything; a script's filename does not always identify its purpose.[8]
Where supported, limit features to relevant templates or load optional functionality after interaction. Check vendor guidance for old theme modifications after uninstalling an app. Work in a duplicate theme, remembering that some app settings affect the whole store.
Ask each feature owner what would stop working if its script disappeared. Review subscriptions, customer service workflows, and marketing measurement before making changes. Disable one candidate at a time in a suitable test setup, compare the result, and retain evidence explaining why the feature stays or goes.
5. Reduce JavaScript work, then schedule it
For suitable external classic scripts, defer allows downloading during HTML parsing and preserves execution order after parsing. async suits independent scripts whose order does not matter. Neither attribute eliminates execution cost. Check dependencies before changing how a theme or app script loads.[9]
Remove unused libraries and duplicate functionality first. Then split optional components, such as advanced galleries or size guides, so their code loads when needed. Preserve essential navigation, variant selection, and purchasing behaviour even when optional features are delayed.
Conceptual execution flow; these blocks do not represent measured timings.
Profile interactions instead of guessing. Break expensive processing into smaller tasks, debounce repeated search requests, and avoid repeatedly measuring and changing layout in the same loop. Verify that quick taps, keyboard navigation, and cart updates still work on slower phones.
On demand loading also needs a good waiting state. If a customer opens a size guide before its module downloads, show clear feedback and handle failures. Avoid attaching duplicate event listeners whenever a section refreshes. Check that one click triggers one action after repeated navigation.[10]
6. Simplify stylesheets and font delivery
Keep styles required for the first screen available when it renders. Delaying all CSS can produce unstyled content and layout movement. Remove obsolete component rules carefully and load specialised styling only where its components exist.[11]
Use browser coverage tools as investigation aids. Code marked unused during one recording may support menus, variants, another breakpoint, or a later interaction. Exercise those states before deleting it.
Reduce unnecessary font families and weights. System fonts avoid font downloads entirely; carefully selected WOFF2 files can support brand typography. Use appropriate font display behaviour, then inspect text during loading. When font swapping causes shifts, match fallback metrics with techniques such as size-adjust, using values measured for the actual font pair.[12]
Review resource hints with the same discipline. Preloading many images, fonts, and scripts makes them compete for attention. Reserve hints for critical resources that otherwise arrive late, verify they match the resource eventually used, and remove obsolete hints left behind by previous theme experiments.
7. Profile Liquid before rewriting templates
Use Shopify Theme Inspector to identify expensive server rendering. Examine complex collection pages, repeated snippets, nested product loops, and unnecessary variant access. With streamed responses, the first byte can arrive before all sections render, so TTFB alone does not describe the entire Liquid cost.[13]
Move calculations that produce the same result outside repeated loops. Fetch and render only the information the visible interface needs. Simplify nested iteration where possible, but preserve product availability, pricing, and variant behaviour. A faster template that displays incorrect information fails the shopping journey.[14]
For example, a shared formatting decision can often be assigned once before rendering product cards. Each product's stock status still belongs to that product. Distinguish invariant calculations from product dependent logic, then compare inspector profiles using representative catalogue sizes rather than a tiny demonstration collection.
8. Reserve space before content arrives
Set image dimensions, stable media aspect ratios, and appropriately sized containers for asynchronous widgets. A review summary inserted above the price can move the entire buying area. Reserve its expected space at each breakpoint, using targeted rules rather than a blanket height for every app block.[15]
Record loading with network throttling and watch announcement bars, recommendations, consent interfaces, and font changes. Keep overlays usable without covering essential controls. Recheck after dismissing banners and switching variants, because a stable first screenshot can hide later movement.
Use calm placeholders that match the finished component instead of arbitrary empty gaps. Measure widget height on narrow screens, where text wraps differently. Reserve enough space for expected content while allowing genuine variation, such as translated labels or longer product names, without clipping important information.
9. Choose your first fix with evidence
| Observed problem | Check first | Verify afterwards |
|---|---|---|
| Hero appears late | Discovery, priority, dimensions, file size | LCP and visual quality |
| Variant selector hesitates | Long tasks and repeated handlers | Interaction trace and selection accuracy |
| Buying button moves | Injected widgets and missing space | Layout stability across devices |
| Large collections render slowly | Liquid loops and excessive data access | Rendering profile and product correctness |
Rank work by affected traffic, customer impact, evidence, and implementation effort. Start with a frequent failure on an important shopping page. Avoid chasing a perfect synthetic score while real customers struggle to select products or use the cart.
Create a short issue record containing the symptom, affected pages, supporting trace, proposed change, owner, and acceptance condition. This makes technical work reviewable for marketing and merchandising teams. It also prevents the same expensive component returning under another campaign name without anyone recognising the tradeoff.
10. Retest, release, and prevent regressions
Compare the same URLs under the same conditions after each meaningful change. Test mobile navigation, search, filters, variants, add to cart, discounts, and the checkout handoff. Confirm analytics still records the intended events without duplicates.
Keep a recoverable theme version, document changes, and monitor real visitor data after publishing. Field reporting needs fresh visits and time to reflect improvements. Set practical performance budgets for images, scripts, and key journeys, then review them whenever you add a campaign, feature, or app.
Agree on what triggers investigation after release, including broken interactions and worsening performance on important templates. Keep a change log so regressions can be compared with deployments and app configuration updates. Review both performance and successful shopping behaviour before declaring the optimisation complete.