Reaching 90+ on Shopify's mobile PageSpeed score is achievable for most stores, but it requires working through every layer in the right order. Scores above 90 on mobile are not about tricks or optimization apps promise in their marketing copy. They come from correctly sized and formatted images, minimal JavaScript, lean CSS, deferred non-critical resources, and a Liquid template that generates fast server responses. This guide shows exactly what pushes stores into the 90+ tier and what keeps most stores stuck below 60.
What Shopify Speed Scores Actually Measure
Before chasing a number, understand what the number represents. Shopify's speed score in your admin and Google PageSpeed Insights are related but not identical.
Found in Online Store > Themes, this score is based on real user data collected by Google's Chrome User Experience Report (CrUX) over the past 28 days. It reflects how real visitors on real devices in real conditions experienced your store. This score moves slowly - a change you make today will not fully appear for up to four weeks. Do not use it to test individual changes.
A lab score generated by running Lighthouse against your URL using a simulated mid-range Android device on a throttled 4G connection. Reproducible within a range (typically plus or minus 5 to 8 points between runs) and responds to changes within minutes. Use PageSpeed Insights for testing individual changes. Use the Shopify speed score and Google Search Console Core Web Vitals for understanding real user experience.
The PageSpeed score is a weighted average of six performance metrics. Understanding this weighting changes how you prioritize fixes. Total Blocking Time is the dominant metric - it measures the total time that render-blocking JavaScript occupied the main thread between FCP and Time to Interactive. Reducing JavaScript has more impact on the overall score than any other single category of fix.
| Metric | Score Weight | Good Target | 90+ Target |
|---|---|---|---|
| Total Blocking Time | 30% | <200ms | <150ms |
| Largest Contentful Paint | 25% | <2.5s | <2.0s |
| Cumulative Layout Shift | 15% | <0.1 | <0.05 |
| First Contentful Paint | 10% | <1.8s | <1.5s |
| Speed Index | 10% | <3.4s | <2.5s |
| Interaction to Next Paint | 10% | <200ms | <150ms |
Where Shopify Stores Lose the Most Points
Dominant problems are almost always App Script bloat and unoptimised images. Stores in this range typically have 15 or more installed apps loading scripts globally, hero images above 500KB, no lazy loading on collection page images, and multiple render-blocking scripts in the head. Fixing just images and removing the most impactful app scripts typically moves stores to the 55 to 65 range.
Foundational issues are partially addressed but not complete. Common remaining problems: a few heavy apps still loading globally, fonts loading without preconnect or font-display swap, JavaScript libraries like jQuery or Swiper loading when native alternatives would work, and no critical CSS implementation. Addressing these moves stores into the 65 to 75 range on mobile.
The foundational work is largely done. Points are being lost to more specific issues: render-blocking third-party scripts that are hard to defer, suboptimal resource loading sequence, LCP image lacking fetchpriority high, and CSS that could be partially inlined. Total Blocking Time is often the remaining dominant issue. Addressing TBT through script deferral, JavaScript splitting, and critical CSS inlining moves stores into the 80 to 90 range.
The remaining points require precise work: the LCP image preloaded with correct srcset hints, all non-critical CSS loading asynchronously, every scroll listener using passive true, no layout-triggering CSS animations, variant data pre-rendered into JavaScript objects, and Liquid templates generating minimal TTFB. The difference between 85 and 92 is often 3 to 4 specific diagnostics each costing 2 to 4 points.
Fixing the Highest-Impact PageSpeed Audit Issues
PageSpeed Insights groups its diagnostics into Opportunities (estimated time savings) and Diagnostics (issues affecting performance but without direct time savings estimates). Fix Opportunities first - they have the most direct score impact.
This diagnostic appears on almost every Shopify store. Render-blocking resources delay FCP and increase TBT, which directly reduces your score on the highest-weighted metric. Fix render-blocking CSS with the preload-then-swap pattern and inline critical CSS. Fix render-blocking JavaScript by adding
defer to all non-critical scripts.<head>
<style>
/* Critical above-fold styles inlined here - 8 to 15KB */
body { margin: 0; font-family: system-ui, sans-serif; }
.site-header { position: sticky; top: 0; z-index: 10; background: #fff; }
.hero { min-height: 400px; background: #f5f5f5; }
</style>
<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>
Every image served larger than its display size wastes download time. PageSpeed lists specific images with their current size and potential savings. Use Shopify's
image_url filter with explicit widths and WebP format, plus a srcset for responsive delivery.<img
src="{{ product.featured_image | image_url: width: 800, format: 'webp' }}"
srcset="
{{ product.featured_image | image_url: width: 400, format: 'webp' }} 400w,
{{ product.featured_image | image_url: width: 800, format: 'webp' }} 800w
"
sizes="(max-width: 768px) 100vw, 50vw"
width="{{ product.featured_image.width }}"
height="{{ product.featured_image.height }}"
loading="lazy"
alt="{{ product.featured_image.alt | escape }}"
>
The Coverage tab in Chrome DevTools shows the percentage of each JavaScript file that executes during page load. Files with low usage percentages are candidates for conditional loading or removal. For app scripts: restrict to relevant page templates using Liquid conditionals. For library scripts (jQuery, Swiper): replace with native alternatives.
Every JPEG or PNG image in your PageSpeed audit is a conversion opportunity. Shopify's CDN converts to WebP automatically when you use
format: 'webp' in the image_url filter. This one change to your Liquid templates fixes this diagnostic across all images simultaneously.Optimizing the Key Metrics for 90+
Target: under 150ms for 90+. TBT is the sum of all long task durations (tasks over 50ms) that occur between FCP and Time to Interactive. Fix by deferring all non-critical JavaScript, splitting large JavaScript files into smaller modules, replacing synchronous app scripts with deferred alternatives, removing unused JavaScript, and replacing JavaScript behavior with CSS where possible.
Target: under 2.0s for 90+. Fix sequence: identify the LCP element (PageSpeed tells you exactly which element), add fetchpriority="high" and loading="eager" to that element, add a preload hint in the head, ensure the image is WebP and sized to display width, add preconnect for Shopify's CDN, and reduce TTFB through Liquid optimization.
Target: under 0.05 for 90+. Fix sequence: add explicit width and height to every img tag, add font-display swap to all custom fonts, reserve space for dynamic content (announcement bars, app widgets), fix any embed or iframe without declared dimensions, and use transform instead of top/left for CSS animations.
Target: under 150ms for 90+. Fix sequence: add { passive: true } to all scroll and touch event listeners, break up long-running JavaScript functions using setTimeout chunking, load app scripts only on pages where they function, replace synchronous event handlers with debounced versions, and initialize non-critical features with requestIdleCallback.
How to Improve the Load Sequence
The load sequence is the order in which resources download and render. An optimal sequence loads the minimum content needed to show something to the visitor as fast as possible, then progressively loads everything else.
<head>
<!-- 1. Preconnects and preloads first -->
<link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
<link rel="preload" as="image" href="{{ lcp_image_url }}">
<link rel="preload" as="font" href="{{ font_url }}" crossorigin>
<!-- 2. Critical CSS inlined -->
<style>/* Critical styles */</style>
<!-- 3. Full stylesheet preloaded asynchronously -->
<link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<!-- 4. Theme JavaScript deferred -->
<script src="{{ 'theme.js' | asset_url }}" defer></script>
<!-- 5. Analytics via GTM - async, independent -->
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"></script>
</head>
Testing with GTmetrix
GTmetrix provides a different view of performance than PageSpeed Insights. Using both gives you a more complete picture.
The first 5 to 10 rows of the waterfall are your critical path. If any of these show long wait times (thin bars that extend far right before the download begins), you have a render-blocking resource or high TTFB issue.
If your LCP image request starts at 1.5 seconds into the waterfall, it was not preloaded. The browser discovered it late during CSS parsing or JavaScript execution. Add a preload hint in the head.
Rows showing simultaneous downloads from multiple external domains (connect.facebook.net, static.klaviyo.com, cdn.hotjar.com) represent your third-party script overhead. Consolidating through GTM reduces these rows to one.
Realistic Benchmarks for Shopify Stores
Iterative Improvement Strategy
Reaching 90+ is not a single optimization session. It is a series of targeted improvements, each measured before moving to the next.
Expected progression for a typical store starting at 35 on mobile:
| Optimization | Expected Score Gain |
|---|---|
| Image optimization (WebP, correct sizing, lazy loading) | +12 to 18 points |
| App audit and heavy script removal | +8 to 15 points |
| JavaScript deferral and critical CSS | +6 to 12 points |
| LCP image preload and fetchpriority | +4 to 8 points |
| Font optimization and CLS fixes | +3 to 6 points |
| Advanced prioritization and TBT reduction | +3 to 8 points |
Avoiding Fake Optimizations
loading="lazy" to your LCP image is one of the most common optimization mistakes. It appears in optimization guides, is easy to implement, and actively worsens the metric you are trying to improve. Your LCP image must use loading="eager" and fetchpriority="high". Check every theme update you apply, as theme updates sometimes change image attributes.Some services detect the PageSpeed bot's user agent and serve a stripped-down version of your store. This inflates your lab score without helping real visitors. Google has countermeasures for detected bot-targeting, and this approach can result in penalization. Real improvements show up in both lab and field data. Fake optimizations show up only in lab data.
A PageSpeed optimization app that loads its own JavaScript bundle on every page is working against itself. Evaluate any speed app by testing your PageSpeed score before and after installation on a development theme - not by reading its marketing claims. The data tells the truth regardless of what the app claims.
Getting to 90+ Final Checklist
Foundation
- All images converted to WebP using
format: 'webp' - All images sized to maximum display width
- Explicit width and height on every img tag
-
loading="lazy"on all below-fold images -
loading="eager"andfetchpriority="high"on the LCP image - All unused apps uninstalled (not just disabled)
- All remaining app scripts restricted to relevant page templates
- All non-critical theme JavaScript using
defer
TBT Reduction (Highest Weight)
- No synchronous scripts in the head without defer or async
- Third-party tracking consolidated into Google Tag Manager
- GTM configured with page-specific triggers (not All Pages for every tag)
- JavaScript Coverage shows no file below 30% usage on this page
- Scroll and touch event listeners using
{ passive: true } - Long tasks under 50ms in Chrome DevTools Performance recording
- No jQuery loaded if theme or app code does not require it
LCP and CSS
- LCP image preload hint in head with imagesrcset and imagesizes
- Preconnect to Shopify CDN in head with crossorigin
- TTFB under 400ms (check with WebPageTest)
- Critical CSS inlined in style tag in head
- Full stylesheet loading asynchronously via preload-then-swap
-
font-display: swapon all @font-face declarations - No CSS animations using top, left, width, height (use transform only)
Verification
- TBT under 200ms in PageSpeed mobile test (ideally under 150ms)
- LCP under 2.5 seconds (ideally under 2.0 seconds)
- CLS under 0.1 (ideally under 0.05)
- INP under 200ms in PageSpeed mobile test
- Tested on real mid-range Android device
- Scores tested with cold cache (WebPageTest First View)
- Lab scores validated against Search Console field data after 28 days
Summary
Reaching 90+ on Shopify's mobile PageSpeed requires solving every layer of the performance stack in the right order. Images first, then JavaScript, then CSS, then LCP prioritization, then TBT reduction, then CLS elimination. Each layer you fix unlocks more improvement from the next.
Avoid fake optimizations that game the score without improving real user experience. Validate every improvement against real user data in Search Console. And set realistic targets based on your store's complexity - a 75 on mobile from a well-managed 12-app store is genuinely excellent performance, even if it is not 90.
For stores looking to systematically work through each optimization layer with Shopify-native tooling, Ecom: Page Speed Expert handles image optimization, script management, and performance monitoring without adding the kind of script overhead that other optimization apps introduce. Work through the checklist. Test every change. The 90+ score is not a secret - it is the result of doing the fundamentals completely.