How to Improve Ecommerce Conversion Using Data and Automation
Measure each step of your own funnel before fixing anything, read what the current evidence says about speed and checkout, and get an alert when a step gets worse.
Most advice on ecommerce conversion starts with the checkout. Your own funnel may point somewhere else, and it is the only data that can tell you which step to fix first.
Averages describe other stores. Littledata’s benchmark of 438 Shopify stores, covering August to October 2026, puts the median conversion rate at 1.3% of sessions, with the top 20% of stores above 2.7% (Littledata). That tells you how far a store is from its peers. However, it does not tell you where the store loses buyers, and benchmarks only compare when they count the same thing: Littledata counts sessions that included at least one purchase, so a figure computed per visitor or per order will differ.
This post shows how to measure each step of your own funnel from a Shopify or GA4 export, which step to fix first, what the current evidence says about speed and checkout, and how to get an alert when a step gets worse. It is written for a store owner or ecommerce manager who can download those reports; the Python is there for anyone who wants to automate it.
Measure your own funnel first
Export five counts for the same period: sessions, product views, add-to-carts, checkouts started and orders. Both Shopify analytics and GA4 report them. Then work out what share of visitors each step loses.
import pandas as pd
# Illustration only: these counts are made up to show the calculation.
# Replace them with your own export for the same date range.
funnel = pd.DataFrame({
"stage": ["Sessions", "Product views", "Add to cart", "Checkout started", "Purchases"],
"count": [10000, 6200, 1500, 820, 310],
})
funnel["share_of_sessions"] = (funnel["count"] / funnel["count"].iloc[0] * 100).round(1)
funnel["lost_at_step_pct"] = ((1 - funnel["count"] / funnel["count"].shift(1)) * 100).round(1)
print(funnel.to_string(index=False)) stage count share_of_sessions lost_at_step_pct
Sessions 10000 100.0 NaN
Product views 6200 62.0 38.0
Add to cart 1500 15.0 75.8
Checkout started 820 8.2 45.3
Purchases 310 3.1 62.2In this illustration the largest loss is between viewing a product and adding it to the cart, where 75.8% of viewers leave. The checkout loses a smaller share of the people who reach it. A store with these numbers would gain little from a checkout redesign until it had worked out why people look at products and do not choose them. Your own export may show the opposite, and the calculation exists to find out.
Read the largest drop
Each step has its own usual suspects. These are places to look first, not diagnoses: a change is only proven by measuring the step before and after it.
| Step that loses the most | What it means | Check first |
|---|---|---|
| Session to product view | Visitors leave before they see a product | Whether the landing page matches the ad or search that brought them; page speed |
| Product view to add to cart | They see the product and do not choose it | Price, delivery cost and delivery time shown on the page, product information, reviews |
| Add to cart to checkout started | They choose and then hesitate | Costs that first appear in the cart; a forced account |
| Checkout started to purchase | They start paying and stop | Payment options, form length, errors, trust in entering card details |
The last two rows have the best evidence behind them, covered in the checkout section below.
Speed: what the evidence shows
The figure most often quoted, that one second of load time is worth 5% to 7% of conversions, has no original source that could be traced, so it is not used here. Two studies are worth knowing, and both have limits.
Deloitte’s Milliseconds Make Millions, commissioned by Google and published in March 2020, found that “retail conversions increased by 8.4% and average order value increased by 9.2%” for a 0.1 second improvement in mobile site speed (Deloitte). It drew on 4 weeks of mobile data from brands in Europe and the US, and it measured a correlation, not a controlled experiment.
More recently, a June 2026 web.dev case study reported that among the Brazilian stores on the commerce platform Nuvemshop, the share with a good Largest Contentful Paint rose from 57% to 96% between January 2025 and January 2026, and session-to-order conversion on mobile Google traffic rose 8.9% over the same period (web.dev). There was no control group, and the platform published it together with Google. It shows that speed and conversion moved together; it does not show by how much one caused the other.
Both point the same way. Neither tells you what a faster page is worth on your own store, which is why the monitoring below tracks speed and conversion side by side.
Measure the metrics Google uses now
Google’s Core Web Vitals are three measurements of real visits (web.dev):
| Metric | What it measures | Good | Poor |
|---|---|---|---|
| Largest Contentful Paint (LCP) | How long the main content takes to appear | 2.5 s or less | over 4 s |
| Interaction to Next Paint (INP) | How quickly the page responds to a tap or click | 200 ms or less | over 500 ms |
| Cumulative Layout Shift (CLS) | How much the layout jumps while loading | 0.1 or less | over 0.25 |
INP replaced First Input Delay as a Core Web Vital in March 2024 (web.dev), so a speed check that still reports FID is out of date.
The PageSpeed Insights API returns two kinds of data. Field data comes from real Chrome users and includes INP; lab data comes from a single simulated page load. A load-only lab run has no interactions to time, so it does not report INP, and Total Blocking Time is the usual lab stand-in (web.dev). The script below prints both.
import requests
PSI = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
def check_page(url, api_key=None):
params = {"url": url, "strategy": "mobile", "category": "performance"}
if api_key:
params["key"] = api_key # the anonymous daily quota is small
data = requests.get(PSI, params=params, timeout=90).json()
# Field data: real Chrome users, when the page has enough traffic.
field = data.get("loadingExperience", {}).get("metrics", {})
for name, metric in field.items():
print(f" field {name}: {metric.get('percentile')} ({metric.get('category')})")
if not field:
print(" no field data: too little Chrome traffic for this URL")
# Lab data: one simulated load.
audits = data["lighthouseResult"]["audits"]
for audit in ["largest-contentful-paint", "cumulative-layout-shift",
"total-blocking-time", "server-response-time"]:
print(f" lab {audit}: {audits[audit]['displayValue']}")
for name, url in {
"Home": "https://yourstore.com",
"Collection": "https://yourstore.com/collections/all",
"Product": "https://yourstore.com/products/your-product",
}.items():
print(name)
check_page(url)Google says it plans to stop including real-user data in this API and points to the Chrome UX Report API instead (Google), so treat the field block as something to move later. For the fixes themselves, see How to Fix Slow Shopify Stores.
Checkout: what shoppers say makes them leave
Baymard Institute puts the average documented cart abandonment rate at 70.22%, the unweighted average of 50 studies published between 2006 and 2025 (Baymard). Its survey of US online shoppers asks why. Setting aside the shoppers who were only browsing, the most common reasons were:
| Reason for abandoning during checkout | Share of shoppers |
|---|---|
| Extra costs too high (shipping, tax, fees) | 40% |
| Delivery too slow | 20% |
| Did not trust the site with their card information | 19% |
| The site wanted them to create an account | 18% |
| Checkout too long or complicated | 17% |
| Website errors or crashes | 17% |
Each reason maps onto something a store can check:
- Extra costs: show delivery costs, and any duties for cross-border orders, on the product page or in the cart rather than at the last step.
- Delivery time: state the delivery estimate before checkout.
- Accounts: confirm in your checkout settings that a customer can buy without creating an account.
- Trust and payment: Shopify supports accelerated checkout buttons for Shop Pay and for providers including Apple Pay, Google Pay, Amazon Pay and PayPal (Shopify).
- Errors: place a test order on a phone after every theme or app change.
Put a value on the fix
Before spending on a change, estimate what it is worth if it works. The function below takes your monthly orders, your current checkout completion rate and a target.
def checkout_value(orders, completion_rate, target_rate, avg_order_value):
"""Monthly and yearly value of raising checkout completion to a target."""
checkouts = orders / completion_rate
extra_orders = checkouts * (target_rate - completion_rate)
return {
"Extra orders per month": round(extra_orders),
"Extra revenue per month": f"CHF {extra_orders * avg_order_value:,.0f}",
"Extra revenue per year": f"CHF {extra_orders * avg_order_value * 12:,.0f}",
}
# Illustration: the made-up funnel above (310 orders from 820 checkouts),
# a hypothetical target of 50% completion and an average order of CHF 127.50.
print(checkout_value(orders=310, completion_rate=0.378, target_rate=0.50, avg_order_value=127.50)){'Extra orders per month': 100, 'Extra revenue per month': 'CHF 12,757', 'Extra revenue per year': 'CHF 153,081'}The target is the uncertain part. Use the rate your own store reached in its best recent month rather than a figure from someone else’s store.
Automate the monitoring
A fix that is not watched tends to wear off: a new app slows the product page, or a theme update breaks a payment button. A weekly report and one alert rule catch most of it.
The report below writes the funnel and the speed results to a spreadsheet each week.
def write_weekly_report(funnel, speed, output_path):
"""Weekly funnel and page-speed sheet."""
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
wb = Workbook()
fill = PatternFill(start_color="2F5496", end_color="2F5496", fill_type="solid")
bold = Font(bold=True, color="FFFFFF")
ws = wb.active
ws.title = "Funnel"
for col, header in enumerate(["Stage", "Count", "Share of sessions", "Lost at step"], 1):
cell = ws.cell(row=1, column=col, value=header)
cell.font, cell.fill = bold, fill
for i, row in funnel.iterrows():
ws.append([row["stage"], row["count"], row["share_of_sessions"], row["lost_at_step_pct"]])
ws2 = wb.create_sheet("Speed")
ws2.append(["Page", "LCP", "INP (field)", "CLS"])
for page, m in speed.items():
ws2.append([page, m.get("LCP"), m.get("INP"), m.get("CLS")])
wb.save(output_path)Alert on change against your own history rather than against a benchmark. A reasonable starting rule is to flag any step whose weekly rate falls more than 15% below its median for the previous 8 weeks, and to flag speed when a page leaves the “good” range in the Core Web Vitals table. Tune the 15% once you have seen a few weeks of normal variation.
import pandas as pd
def step_alerts(weekly_rates: pd.DataFrame, max_drop=0.15, weeks=8):
"""weekly_rates: one row per week, one column per step (rates from 0 to 1)."""
baseline = weekly_rates.iloc[-(weeks + 1):-1].median()
change = weekly_rates.iloc[-1] / baseline - 1
return change[change < -max_drop]For building the full report pipeline, see How to Build Automated Reporting for Ecommerce Stores.
flowchart LR M[Measure each step] --> D[Find the largest drop] D --> F[Change one thing] F --> W[Watch that step against its baseline] W --> M
Why small gains add up
Conversion is the product of the step rates, so improvements multiply. Using the illustration’s four step rates:
steps = [0.62, 0.242, 0.547, 0.378] # step rates from the illustration
better = [s * 1.10 for s in steps] # each step improved by 10%
def product(xs):
total = 1.0
for x in xs:
total *= x
return total
print(f"Now: {product(steps) * 100:.2f}% After: {product(better) * 100:.2f}%")
print(f"Change: {(product(better) / product(steps) - 1) * 100:.1f}%")Now: 3.10% After: 4.54%
Change: 46.4%A 10% gain at each of four steps raises the end-to-end rate by 46.4%, because 1.1 multiplied by itself four times is 1.46. However, the same arithmetic works against you: a 10% loss at one step costs 10% of all orders, which is why the alert rule watches every step and not only the total.
What to do this week
Export the last 8 weeks of funnel counts, calculate the share each step loses, and pick the largest. Change one thing at that step, and watch that step’s rate for the following 4 weeks against its 8-week baseline. Compare the result with the 1.3% median only at the end: the benchmark tells you how far there is to go, and your own funnel tells you where to start.
Ecommerce optimisation services cover this cycle end to end: a performance audit, a funnel analysis and automated monitoring.
Get in touch to discuss your store’s funnel.
Enjoyed this article?
Get notified when I publish new articles on automation, ecommerce, and data engineering.
Get in touch