For one day I thought we had emailed every customer in a client's order history about parcels that shipped three years ago.
Mid-way through merging one country store into another, a signal in the logs suggested the imported historical orders had fired order confirmations. Years of orders. The founder said it gave him a headache. I said I was scared, which was accurate.
I went back through every order, in batches, against the notification log. It was a one-off signal. Nothing had gone out. Five or ten orders had genuinely failed on import and we re-ran those.
What made that day survivable is something I'd learned on an earlier migration: Shopify lets you edit the order confirmation template in admin and gives you no switch to turn it off. The only way to silence it for an import is through the API, before the first row goes in. Same for every order-created automation and webhook, each of which will happily act on a five-year-old order the moment it's created.
So four lines sit above "start import" on every bulk write now: notifications suppressed via API, automations paused, webhooks listed and disabled, imported orders tagged so anything that wakes up later can exit early.
The check-every-order part wasn't clever. It was the only thing I trusted by then, and it's why I sleep on import days now. Mostly.
Pitched a two-pack on a launch call this week. Killed it myself ten minutes later.
The product shows two effects immediately and the rest at six weeks. One unit lasts six to eight weeks. My pitch: two-pack plus a money-back guarantee, lifts AOV on the first order.
Then I did the maths from the buyer's side. If one unit delivers the six-week result, why would anyone buy two. The second unit had no job.
We moved to a stacked gift over a threshold instead, a bestseller sample set plus free shipping. Multipacks were my default offer for years. I no longer think they are an offer on their own.
The landing page said one price. The cart said another.
The previous developer had typed the discount into the theme settings. So the number on the page was real in the sense that it rendered, and fictional in the sense that nothing downstream had ever heard of it. No code, no automatic discount, no script. Just text.
Everyone who trusted that page found out at the cart, which is the worst place to learn the truth about a price. It's the exact moment they're deciding.
Before I'll touch an offer on any store now, I take the headline offer from the highest traffic page, add the product to cart, and go all the way to the payment screen. If the number that survives isn't the number on the page, the offer doesn't exist yet, and there's nothing above it worth optimising.
Two of us spent most of a call reverse engineering a legacy site's related-products rule so we could rebuild it on Shopify.
Recommendation table, a component table with the same shape, a typology field, a type field, group names. Then we counted. The rule had been written for 26 products.
The lead said just use Shopify's own recommendations, it's probably better than what they have. I said I'd never tested that engine and couldn't promise it was.
We went with the native one. Twenty-six products of logic had been holding a workstream for a fortnight.
The lesson wasn't that Shopify is better. It's that nobody had checked how much the clever system actually did before rebuilding it exactly. First question on any legacy feature now is a count: how many products, orders or pages does this rule actually touch? Under a few hundred, the native option gets tested first.
Want an extra 15% increase in conversion rate in the next 90 days ?
Iโve develop a Scalable Data-Driven CRO system just for that
Get your personalize strategy here ๐
https://t.co/m12ZvzB6Iv
I don't pay developers by the hour. A better developer finishes faster, and hourly quietly punishes him for it. Per project, his incentive and mine point the same way: done, and correct, with nobody counting minutes.
What it creates is a hiring problem. A project rate only works if you can tell quickly whether someone is good, and a CV can't tell you that. Neither can an interview, in my experience. I've hired confident interviewers who couldn't ship, and quiet ones who could.
So instead of a longer interview, one small paid trial on a real deadline. Real client work, scoped to a day or two, paid at the rate we'd actually work at, with the deadline I'd actually give.
Then three things: did it land on time. Did it work the first time. Did they ask a question before building the wrong thing.
Paid, always. An unpaid trial mostly selects for whoever has nothing better to do that week.
Under $5M, the order I run CRO in: offer, then speed and friction, then everything else.
I ran it the other way round for two years. Heatmaps, button tests, sticky add to cart, on stores where the offer was 10% off and the PDP took five seconds. The tests were fine. They were measuring a store nobody wanted to buy from yet.
Prettiest store I audited this year had the worst numbers in the batch. I have said the ugly thing before, so here is what the pretty one looked like from inside.
Editorial photography, custom type, a homepage that could win an award. PDP loads in 6.8 seconds on a phone. The offer, once you scroll to it: 10% off your first order, free shipping over $75.
Ugliest store in the same batch: stock theme, one font, a bundle with a guarantee in the first screen, PDP in 2.1 seconds.
Seven seconds to be offered 10%. The photography never got a chance to matter.
A partner agency sent me an audit to quote on. Two of the headline items were already done.
"Convert all images to WebP": Shopify's CDN serves WebP by default. "Fix layout shift": the live site measured CLS 0.1 that afternoon, which passes.
Nobody had run the site through a tool before writing the list. I sent back a screenshot of it passing and a quote for the three items that were real.
Most speed checklists I get handed were written from a template. I have written a few of those myself.
$341,544 in sales, September to November 2025, up 65% on the quarter before. Second screenshot: AOV $66.35, up 26%. Same store, same window.
Uncomfortable part first: that window has Black Friday in it and the one before doesn't. Some of the 65% is the calendar and I can't tell you how much.
The AOV line is the one I'd stand behind. A busy season brings more orders. Discounts usually make each order smaller. These went 26% bigger. That's the cart changes: a bundle as the default add, a second item suggested from pairings people were already making, free shipping repeated in the totals.
One screenshot says a good quarter happened. The pair says which part of it we did.
I asked a client for a testimonial after a migration and walked away with nothing I could use.
Not because he was unhappy. He was happy. He just couldn't tell me what had improved, and honestly neither could I. The old store's analytics had been half broken for a year before we arrived: events firing twice, traffic self-referring, revenue in the platform not matching revenue in analytics. We fixed all of it as part of the rebuild.
Which means before and after weren't measuring the same thing. Any comparison I put in a case study would have been wrong in a way I had no way of catching.
So now it's week one, before anything gets rebuilt: fix the tracking on the OLD store first, then let it run clean for a month. That month is the baseline, and it's the only version of "before" worth anything.
It also ships while the old store is still live, so the client gets something real out of week one instead of waiting.
Page builders solved a real problem. Marketing couldn't wait a week for a developer every time an ad needed a landing page.
AI has mostly finished that job. Describe a page, have it live the same afternoon, and honestly most of what I see come out of it is fine. Fast, clean, converts.
The problem moved instead of disappearing. The AI-built landing page and the rest of the store look like two different companies. Different type scale, different button shapes, different padding, a hero that follows no rule the brand uses anywhere else. Each page is good. Put four next to the actual store and the brand falls apart.
A customer doesn't see one page. They see the landing page, then a PDP, then the cart, and the joins are where trust leaks.
Before any AI-built page goes live I open it beside a PDP and check four things against the theme: type scale, button radius and height, section padding, brand colours by hex. Anything that doesn't match gets pulled to the theme's values. Twenty minutes, and it's the difference between a fast page and a page that belongs to the store.
Every tracking event on their site was firing into an account they couldn't open.
New client, decent setup, tag manager doing real work, all of it living inside a Google account owned by the agency before us. The founder never had access, never thought to ask, and that relationship hadn't ended warmly.
So we could see data. We couldn't change what was collected, fix a broken event, or add anything. Nothing in their own Shopify admin could even tell us what was inside that container.
Day one with a new client now, before any tracking work gets quoted: the client creates Tag Manager, Analytics and Search Console themselves, on their own domain email, and adds us as a user. Never the other way round.
If the accounts already exist, the first task is confirming the founder is an owner on each one. Not an editor. Owner.
Ten minutes at the start of a relationship. Basically unrecoverable at the end of one.
The client had roughly 600 products to migrate. About 60 of them were things a customer could actually buy.
The rest were components and add-ons. A handle that only exists as part of a set. An engraving option. A gift note. On the old platform they lived as products, because a product was the only object the platform had, and the storefront quietly hid them.
We migrated all 600 as products. Which meant a customer could land on a URL and buy an engraving, on its own, with nothing to engrave.
Before a single product gets mapped on a replatform now, I export the whole catalogue and sort it into three columns: sellable on its own, component of something sellable, add-on or option. Only column one becomes a product with its own URL. The other two become variants, line item properties or bundle components.
600 rows took an afternoon to sort. Hearing about it from a customer takes a lot longer.
A founder sent me a PageSpeed mobile score of 41 this week and asked how bad it was.
The number I actually look at is further down the same page. PageSpeed leads with lab data: one simulated load on a throttled mid-range phone in a data centre. Below it, when a store has enough traffic, sits the Chrome UX Report. Real Core Web Vitals from real visitors over the last 28 days, on their own devices. Shopify's own speed report reads from the same place, which is exactly why it disagrees with Lighthouse.
That store's field data had most real visitors loading the PDP in under three seconds. The 41 was describing a phone nobody in their customer base is holding.
So when a client worries about speed now, I open CrUX first and read LCP, INP and CLS at the 75th percentile. If the field data passes, the lab score is a to-do list, not an emergency, and we work it in priority order. If there's no field data at all, the store doesn't have the traffic for speed to be the bottleneck, and the offer is the better place to spend the month.
When I first started doing migrations, we were ten weeks into a Woo to Shopify rebuild before anyone noticed the old store priced by the visitor's IP.
A shopper in one country saw one price. A shopper in another saw a different price, same product. It wasn't in the brief, the designs, or a single call.
We assumed Shopify couldn't do that, because it doesn't price by raw IP. That assumption was half wrong. Shopify Markets does show different prices by region, through markets and catalogs. What it doesn't do is reproduce a rule nobody documented, on a store where nobody had set the markets up.
The honest part: I never asked. I read the brief, saw one price, and assumed that was all the pricing there was.
Before a single page gets built on any replatform now: open the old store from three countries through a VPN, same product, logged out. Then logged in. Then with a coupon. Any price that moves is a workstream nobody scoped, and on Shopify it's a market and a catalog to configure, not a setting to switch on.
The check costs a day in week two. We found it in week ten, and it held up every market except the two closest to launch.
Six weeks of support tickets. Twenty minutes on one screen share.
A subscription app was behaving one way for the client and a different way for us. We raised a ticket. Two days later the same question got a different answer from the same vendor.
I kept writing tickets for weeks after that, because a ticket feels like progress and asking everyone for a meeting feels like an imposition.
Then we got the client, my developer and the vendor on one screen at the same time. It took twenty minutes. Most of it was three people realising they'd been using the same word for three different things.
Two different answers inside a week is not bad support. It means the question hasn't landed. And a written thread is the worst possible place to find that out.
Now that's the trigger: two contradictory answers, stop typing, book the screen share. Client on it, developer on it, everyone sharing an actual screen instead of describing one.
I'm about as anti full store redesign as it's possible to be right now.
Not redesign work in general. Rebuilding a PDP, fixing the cart, reworking the offer above the fold, that pays for itself and I do it most weeks. A full teardown and rebuild of the whole store is a different animal. Every one I've watched, including ones I was paid to run, ships a better looking store and about the same conversion rate.
The quotes give it away. Every redesign quote I've read this year, mine included, lists pages and sections. Not one lists a number the work is meant to move.
So before I quote a full redesign, the founder has to finish this sentence: after launch, X goes from A to B because the new store does Y. Speed is a Y. A real offer above the fold is a Y. A cleaner grid doesn't finish the sentence.
If you've run a full redesign that moved a real number, I'd genuinely like to see it. I've been asking for a while and haven't found one.
Two fonts is the ceiling I hold every store to.
Last audit: four font families, 11 weight files, 380KB downloading before a product image appeared. The fourth family was used in exactly one place, a seasonal hero headline.
It's almost always that story. A banner, a campaign, one week a designer needed something, and then it sits in the theme for two years, downloaded by everyone.
That headline is an SVG now. Same look, one small file, nothing blocking render. The designer was unhappy for about a day.
Count files, not families. Four weights of one family is four downloads. I list every font file the theme loads, with its weight, and find where each one actually appears on the live site. If it shows up in fewer than three places, it becomes an image or it goes.