DEV Community

OPTTOYSCHINA
OPTTOYSCHINA

Posted on

Alibaba scores 51 on PageSpeed. Our full 389,000-product marketplace scores 93. The dirty secret is m-dot sites

Every number below links to a permanent Google PageSpeed Insights report — click and verify.

Every frontend engineer has quietly suspected this: the "mobile speed" of big e-commerce is a bit of a lie. You run the numbers on a giant — pretty scores. You open the actual site — something doesn't add up.

We build a B2B toy marketplace (389,000 products, 5,233 factories, multi-currency, search-by-photo), and along the way we ran every major B2B platform in the world through Google PageSpeed Insights. Same method, same day, permanent links saved.

Turns out the suspicion is true. And it's funnier than we thought.

The dirty secret: the m-dot

Mobile PSI scores, homepages, July 2026:

Platform Score What was actually measured
IndiaMART 98 ⚠️ m.indiamart.com — a stripped-down mobile shell
our marketplace 93 the full site, 389k products
Made-in-China 86 ⚠️ m.made-in-china.com — same trick
Ozon 70 full site
DHgate 65 full site
Alibaba 51 full site
Amazon 48 full site
eBay 42 full site
GlobalSources 30 full site

Notice the pattern? Everyone above 70 — except us — serves Google's mobile agent a separate m-dot site: a gutted shell with a bare menu and no real catalog. PSI honestly discloses it in a small banner ("Results for URL: m.…") — but who reads the banner?

So the industry recipe for mobile speed in 2026 is: hide your site, show Google a menu. The only way to post high numbers with your real site is to actually make it fast.

Here's how we did it — including the embarrassing parts: the "PSI lottery", a double-markup pricing bug, and a globe video that fell asleep forever.

Why we deliberately did NOT build a native app

Everyone told us to build an app. We refused — on purpose. Here's the math.

An app is invisible to Google. Every user has to be bought: app-install campaigns, social ads, store featuring. That's why e-commerce companies burn millions of dollars a year on Instagram and TikTok ads — their catalog lives in a black box that search engines can't see.

A fast full website is the opposite: all 389,000 product pages are indexed, every one of them is a free landing page. A wholesale buyer googles an obscure toy at 2 a.m. — and lands on our product card, in his currency, with a WhatsApp button. Zero ad spend on that acquisition. Multiply by the catalog size: the site IS the marketing department. The cheapest and fastest customer acquisition channel we know is a product page that loads in under a second and ranks.

But — and this was the hard requirement — the mobile web experience had to feel like an app, not like a compromise: sub-second paint (93/100 mobile on the FULL site), camera-based photo search right from the toolbar, prices in your local currency auto-detected, one-tap WhatsApp/Telegram to close the deal. No 200 MB download, no App Store gatekeeping, no "please sign up to view". One tap from a Google result or a shared link — straight into the catalog.

Google ranks Core Web Vitals. So for us speed isn't a vanity metric — it's the acquisition engine. That's also why the m-dot trick above is self-defeating: you show Google a fast shell, but the pages that could actually rank and sell live on the slow "real" site.

Boring architecture, fast results

  • Front: Astro SSR on Cloudflare Workers. Vanilla JS on the client. No React on the storefront: a product card is an HTML string from a template, not a hydrated component.
  • Back: Python/FastAPI + Postgres + Redis.
  • CDN: Cloudflare.

Nothing heroic. The speed came from discipline around three things: caching, the critical render path, and the database.

The forever-warm site: nobody ever waits for a render

Classic SSR pain: while the page builds on the server, the user stares at a blank screen. Classic cache pain: once a day it expires and some unlucky visitor eats a cold render.

We killed both with one construct — SWR (stale-while-revalidate) in the edge middleware:

  1. Request comes in → serve the cached copy INSTANTLY, even if it's stale.
  2. In the background (waitUntil), rebuild the page and store the fresh one.
  3. s-maxage=86400 + Tiered Cache + Cache Reserve — the copy lives a day, regional PoPs pull from the upper tier, the reserve keeps a central copy.

Result: the phrase "cold start" is dead. FCP is a stable 1.4s in every run, from anywhere. We verify with external monitors in 7 regions (London, Sydney, Warsaw, Dallas, Amsterdam, Tokyo, Mumbai) — which double as free uptime monitoring and cache warmers.

Personalization vs. caching: the dilemma everyone solves wrong

A marketplace must show tenge in Almaty, som in Tashkent, yuan in China, dollars in Berlin. The usual answer is server-side geo rendering. Congratulations, your cache is dead: one copy per country, most of them cold.

Our answer: fully neutral SSR. One HTML for the whole world → one cache → always warm. On the client:

// the CDN already knows the geo — ask it for free
fetch('/cdn-cgi/trace')  country=KZ  sessionStorage
// prices in HTML are a neutral base; the client multiplies by the local rate
Enter fullscreen mode Exit fullscreen mode

No flash of wrong currency (rates apply before prices paint), repeat visits read the country from sessionStorage instantly. Personalization lives — and so does the cache.

Killing the PSI lottery

The most maddening part: the score bounced — 76, 90, 97, 80 — same page. We identified the culprits by name from Lighthouse reports:

Lottery #1 — CLS. A "recently viewed" strip loaded late and shoved the grid down: CLS 0.098. The fix is boring and bulletproof: reserve the height (min-height + :not(:empty)), zero markup reshuffling. CLS is now 0.001–0.006 in every run.

Lottery #2 — the LCP element. Lighthouse alternated between the headline and the globe video poster — and when the poster travelled the network on a cold PoP, the score tanked. Fix: the mobile poster is inlined into the HTML as a 6 KB data-URI webp — zero network dependency, it paints on the first frame even on an ice-cold PoP. The 4 MB globe video only starts after window.load + idle, so it physically can't compete with the first screen.

HTML diet. 252 KB → 127 KB (41 gzipped): the photo-search modal (50 KB) moved to a separate deferred chunk. (We over-did it once, shipped the header CSS as a blocking file, caught FCP 1.4→1.8, and put the critical CSS back inline. Honesty included.)

Result: mobile 93-93-93-93 and desktop 99 (FCP 0.8s / LCP 0.9s / TBT 0). Honest caveat: 1 run in 5 still catches a 77 — the poster on a rare cold node. We know, we're fixing it, and we're not hiding it: that's the difference between a measurement and marketing.

The database: 389k products, 18 ms sorting

Sorting the catalog by price took 9,663 ms — the price lived in a JSON field of the main table while filters lived in another. The classic:

  1. Denormalize price/moq into the filter table (the one already doing the WHEREs).
  2. A trigger keeps the mirror in sync.
  3. Partial indexes for the storefront's actual queries (idempotent migrations, CONCURRENTLY, zero downtime).

9,663 ms → 18 ms. A giant factory page (36,000 products from one supplier): 2,270 ms → 5 ms.

Search: Russian+English FTS + trigram typo tolerance + vector fallback + an anti-hallucination gate (so "doll" doesn't bring trucks). Hot queries hit a Redis cache of the full response: warm search over 389k products in 6–15 ms. Iron rule: the top-20 before and after any optimization must be identical on a control set — speed bought with relevance is a regression.

A living storefront that doesn't kill the cache

We wanted the homepage to breathe — a new slice of bestsellers every 5 minutes. The naive way (random offset per user) kills the API cache. Ours:

window = floor(now / 5min) % pool   // every user on Earth gets the same window
Enter fullscreen mode Exit fullscreen mode

Everyone sees the same slice → Redis serves it from memory → the storefront is alive at zero cost. (Follow-up rake: the window must respect your anti-bot depth cap, or the block silently starves once in a while. Invariant: window ≤ min(pool, cap).)

Beyond speed: photo search and AI search

Search by photo — on the mobile WEB. Here's the part we're genuinely proud of. Camera search has always been a privilege of the giants' native apps: Alibaba has it in the app, Amazon has it in the app. On the mobile web, among B2B marketplaces, we couldn't find it at anyone. So we shipped it on the site itself: a wholesale buyer at a trade fair in Guangzhou — or standing in a competitor's shop — snaps a picture of a toy, and the platform finds it and its analogs across 389k products from 5,233 factories. No app install, no account wall — one tap from the toolbar. And it costs the speed budget nothing: the camera modal weighs 50 KB and loads as a deferred chunk. The feature exists; LCP never notices. This is the whole thesis in one feature: everything "only an app can do" — done on the web, indexed by Google, one link away.

AI search. "soft toys for toddlers under 3, wholesale" is understood by meaning, not keywords — FTS + vectors + that anti-hallucination gate. Typos are healed by trigrams.

Geo multi-currency (covered above — it coexists with caching) and WhatsApp/Telegram straight from the product card: a B2B deal starts in one tap.

Speed is money (not our numbers — sourced)

  • Deloitte × Google, "Milliseconds Make Millions" (web.dev case study): a 0.1s mobile speed improvement lifts retail conversions by +8.4% and average order value by +9.2%.
  • Google's industry benchmarks: going from 1s to 3s load raises bounce probability by 32%; more than half of mobile users abandon slow pages.
  • The classics: Amazon's 2006 finding that every extra 100 ms costs ~1% of sales; Akamai's retail report: −7% conversions per 100 ms of latency.

Now overlay that on our catalog: warm search in 6–15 ms, sorting in 18 ms, desktop LCP at 0.9s. These aren't sport records — they're the cash register.

The rakes nobody blogs about

  • Double markup. The backend marked prices up; the frontend marked them up again. The storefront quietly overcharged 15% vs. the admin panel until we reconciled one SKU digit-by-digit across every surface (card/PDP/cart/search, 4 currencies). Lesson: price is an invariant verified by a matrix, not "by eye".
  • Clobbering shared files. Two devs edit one JS file; the second deploy erases the first one's fix. Twice in one week. New law: every report states "started from version X".
  • Bump the version or die. Changed a static file's content? Change its query version. Otherwise the cache will faithfully serve the old file while you spend a week "fixing" what's already fixed.
  • The globe fell asleep forever. The hero video played 8 seconds — then the browser paused it (power saving), and the poster had already faded out: an eternal freeze-frame. Chrome does this more aggressively on older machines. Fix: self-healing (re-play on visibilitychange/focus; after 3 refusals bring the poster back) + a failure telemetry beacon, so we treat real machines with data instead of guesses.
  • iframe mobile emulation lies. Touch mechanics and coarse pointers don't emulate in an iframe — a "bug" visible only in DevTools may not exist on a real phone. Mobile behavior is verified by a human with a phone, period.

Receipts — permanent Google PSI reports (click and verify)

Our runs (mobile, one-day series): run 1 · run 2 · run 3 · run 4 — the honest floor · run 5 + desktop 99

Competitors (same day, same method): IndiaMART 98 — see the "Results for URL: m.indiamart.com" banner · Made-in-China 86 — same m-dot swap · Alibaba 51 — full www


Who we are: a small team behind *opttoyschina.com*. The scale, in numbers: 389,000 products · 5,233 factories · shipping to 58 countries · prices auto-detected by geo · a copy of the site warm in ~300 datacenters worldwide · uptime watched from 7 regions. No enterprise budget, no VC — just caching discipline, honest measurements, and 389,000 pages that rank. Don't take our word for any of it: paste opttoyschina.com into pagespeed.web.dev right now and run your own audit. Questions, arguments, and your own cases (store, marketplace — whatever hurts) — comments or DMs are open. If there's interest, we'll write up the 6 ms search, deterministic storefront rotation, and video failure telemetry as separate deep dives.

Top comments (0)