<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: OPTTOYSCHINA</title>
    <description>The latest articles on DEV Community by OPTTOYSCHINA (@opttoyschina).</description>
    <link>https://dev.to/opttoyschina</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4033582%2Fe6b65ee6-7dc5-4f59-832f-2d88597674b3.png</url>
      <title>DEV Community: OPTTOYSCHINA</title>
      <link>https://dev.to/opttoyschina</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/opttoyschina"/>
    <language>en</language>
    <item>
      <title>The carton is the unit: what 385,000 wholesale listings say about minimum order quantity</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Tue, 22 Sep 2026 09:45:49 +0000</pubDate>
      <link>https://dev.to/opttoyschina/the-carton-is-the-unit-what-385000-wholesale-listings-say-about-minimum-order-quantity-la3</link>
      <guid>https://dev.to/opttoyschina/the-carton-is-the-unit-what-385000-wholesale-listings-say-about-minimum-order-quantity-la3</guid>
      <description>&lt;h1&gt;
  
  
  The carton is the unit: what 385,000 listings say about minimum order quantity
&lt;/h1&gt;

&lt;p&gt;Every new importer asks the same first question — "what is your minimum order?" — and expects a number in dollars. It almost never is one. The minimum is a physical fact: how many pieces the factory puts in one box.&lt;/p&gt;

&lt;p&gt;We run a wholesale catalogue of &lt;strong&gt;385,341 listings from 5,530 Chinese factories&lt;/strong&gt;. Here is what the numbers actually look like when you measure them instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;From a 6,000-item sample of the live catalogue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;median price per piece: &lt;strong&gt;¥9.30&lt;/strong&gt; (about $1.45 at our published rate)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;52%&lt;/strong&gt; of items are under ¥10 a piece&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;92%&lt;/strong&gt; are under ¥50&lt;/li&gt;
&lt;li&gt;median minimum order: &lt;strong&gt;84 pieces&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the common carton sizes: &lt;strong&gt;24, 36, 48, 72, 96, 120&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those carton sizes are not a pricing tier. They are the factory's packing line: a large radio-controlled truck comes twelve to a carton, small figures come several hundred. The box is the unit, and nobody downstream gets to redefine it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "one piece" is not cheaper for anyone
&lt;/h2&gt;

&lt;p&gt;Breaking a carton sounds like a favour to a small buyer. It is not, for three reasons that all cost money:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The remainder becomes dead stock.&lt;/strong&gt; Nineteen pieces of a twenty-piece carton sit in a warehouse until someone wants exactly nineteen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The factory packaging is destroyed.&lt;/strong&gt; That packaging is what lets the goods be sold as new.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The handling cost per piece explodes.&lt;/strong&gt; Picking, repacking and re-labelling one item costs the same as handling a whole carton.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the honest answer to "can I order one?" is no — and the useful answer is that a &lt;em&gt;test carton&lt;/em&gt; of a cheap model costs less than most people expect. At a median ¥9.30 and a median carton of 84, a typical test is around ¥781, roughly $122 at our rate. That is the real entry price of wholesale, and it is a fifth of what most first-time buyers assume.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes about buying
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Buy width, not depth, on the first order.&lt;/strong&gt; Twenty models at one carton each tells you what sells. One model at twenty cartons tells you nothing until it is too late.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price per piece is the wrong comparison number.&lt;/strong&gt; Compare price per carton against the shelf space the carton will occupy and the weeks it will take to clear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimum order value is a separate question from minimum order quantity.&lt;/strong&gt; We do not have a minimum order value at all: one carton of one model is a valid order. Many suppliers do have one, and conflating the two is how buyers end up ordering four times what they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody budgets for
&lt;/h2&gt;

&lt;p&gt;The carton decides freight too. Freight is charged on gross weight or volumetric weight, whichever is greater, and cheap bulky toys are almost always volumetric. A carton of ¥11 plush is light and enormous; a carton of ¥11 die-cast cars is small and heavy. The same money buys wildly different freight bills, and you find out at the quote, not at the order.&lt;/p&gt;

&lt;p&gt;Transit times matter for the same reason: sea is the cheapest and the slowest, and the ranges run from 2 days to Hong Kong to 45 days to Chile. We publish them per country for &lt;a href="https://opttoyschina.com/en/shipping-and-transit-times/" rel="noopener noreferrer"&gt;62 destinations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And one date overrides all of it: &lt;strong&gt;Chinese New Year falls on 6 February 2027&lt;/strong&gt;, with roughly six weeks of disrupted output around it. The backward count from your shelf date is on our &lt;a href="https://opttoyschina.com/en/china-toy-production-calendar/" rel="noopener noreferrer"&gt;production calendar&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The catalogue itself, with per-piece and per-carton prices on every item, is at &lt;a href="https://opttoyschina.com/" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>data</category>
      <category>business</category>
    </item>
    <item>
      <title>Stop using a generic FX API for prices. Use the central banks.</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Mon, 21 Sep 2026 14:45:07 +0000</pubDate>
      <link>https://dev.to/opttoyschina/stop-using-a-generic-fx-api-for-prices-use-the-central-banks-4of9</link>
      <guid>https://dev.to/opttoyschina/stop-using-a-generic-fx-api-for-prices-use-the-central-banks-4of9</guid>
      <description>&lt;p&gt;Our catalogue is priced in one currency — Chinese yuan — and read by buyers in dozens of countries. Somewhere between the database and the buyer's screen, ¥9.30 has to become a number they recognise, and it has to be the &lt;em&gt;same&lt;/em&gt; number on every reload.&lt;/p&gt;

&lt;p&gt;That turns out to be three separate problems, and the usual answer ("call an FX API") solves none of them well. Here is what we ended up with, including the two bugs that cost us the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem one: which rate is the real rate
&lt;/h2&gt;

&lt;p&gt;A generic FX API gives you a mid-market rate aggregated from somewhere. For a price a buyer will act on, that is the wrong number, for a boring reason: &lt;strong&gt;your buyer's accountant does not use it.&lt;/strong&gt; They use whatever their central bank published this morning, because that is what the invoice will be checked against.&lt;/p&gt;

&lt;p&gt;So we take each currency from the institution that actually sets the reference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Currency&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CNY → USD&lt;/td&gt;
&lt;td&gt;People's Bank of China, central parity (&lt;code&gt;ccpr.json&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RUB&lt;/td&gt;
&lt;td&gt;Central Bank of Russia&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KZT&lt;/td&gt;
&lt;td&gt;National Bank of Kazakhstan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UZS&lt;/td&gt;
&lt;td&gt;Central Bank of Uzbekistan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AED&lt;/td&gt;
&lt;td&gt;USD peg, 3.6725 — it is a peg, not a market&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The AED line is the interesting one. There is no point calling an API for a currency that has been pegged at 3.6725 to the dollar since 1997. We derive it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Дирхам привязан к доллару фиксированно. Тянуть его из «курсовой» ленты —&lt;/span&gt;
&lt;span class="c1"&gt;// значит каждый день заново узнавать одно и то же число, и однажды получить шум вместо него.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AED_ZA_USD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.6725&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;USD&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;AED_ZA_USD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One source of truth, no drift between two feeds that should agree.&lt;/p&gt;

&lt;p&gt;Everything else falls through to &lt;strong&gt;USD&lt;/strong&gt;. That was a deliberate narrowing: we used to localise currency for far more countries, and every extra currency was another surface where the price could be wrong. A buyer in Chile reading a dollar price is not confused. A buyer in Chile reading a &lt;em&gt;wrong&lt;/em&gt; peso price is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem two: the rate is not the price
&lt;/h2&gt;

&lt;p&gt;A published price is not the raw rate. Ours is two multipliers on top, and keeping them separate matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// два множителя, и они НЕ взаимозаменяемы:&lt;/span&gt;
&lt;span class="c1"&gt;//   product_pct — наша наценка на товар, экономика&lt;/span&gt;
&lt;span class="c1"&gt;//   currency_pct — запас на курс: комиссии банков и движение курса за сутки&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opublikovannyi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;syroi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;currency_pct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Today's live numbers from our own feed, straight out of the endpoint:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Currency&lt;/th&gt;
&lt;th&gt;Raw&lt;/th&gt;
&lt;th&gt;Published&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;USD per CNY&lt;/td&gt;
&lt;td&gt;0.14810207&lt;/td&gt;
&lt;td&gt;0.155507&lt;/td&gt;
&lt;td&gt;×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KZT&lt;/td&gt;
&lt;td&gt;66.68&lt;/td&gt;
&lt;td&gt;70.014&lt;/td&gt;
&lt;td&gt;×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RUB&lt;/td&gt;
&lt;td&gt;12.567&lt;/td&gt;
&lt;td&gt;13.19535&lt;/td&gt;
&lt;td&gt;×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UZS&lt;/td&gt;
&lt;td&gt;1767.71&lt;/td&gt;
&lt;td&gt;1856.0955&lt;/td&gt;
&lt;td&gt;×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AED&lt;/td&gt;
&lt;td&gt;0.54390486&lt;/td&gt;
&lt;td&gt;0.5711&lt;/td&gt;
&lt;td&gt;×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 15% product markup lives in the product price, not in the rate. Two reasons, both learned the hard way: the markup changes for commercial reasons and the buffer changes for financial ones, and if they share a variable you can no longer answer "why did this price move?" — which is the only question anyone asks about prices.&lt;/p&gt;

&lt;p&gt;The rates refresh once a day, 06:30 UTC, on a timer. Not per request. A price that changes while a buyer is reading it is a price they will not trust, and a central bank publishes once a day anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem three: the flicker
&lt;/h2&gt;

&lt;p&gt;This is where we shipped the actual bug, and it is worth describing because it is invisible in code review and obvious to a customer.&lt;/p&gt;

&lt;p&gt;Country detection is asynchronous — we read it from the edge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OTC_COUNTRY_READY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/cdn-cgi/trace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;razobrat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means at first paint you do not know the country. The naive flow is: render in a default currency, then swap when detection resolves. So a buyer in Dubai loaded the page, saw &lt;strong&gt;tenge&lt;/strong&gt;, and watched it turn into dirhams a beat later. From their side that is not a loading state. That is a site that just showed them someone else's price.&lt;/p&gt;

&lt;p&gt;Two fixes, and both were needed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The default has to be the global one, not the home one.&lt;/strong&gt; Our fallback was the domestic currency because the domestic market came first historically. Wrong default: a wrong dollar price is a rounding argument, a wrong tenge price is a different country.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VALYUTA_PO_UMOLCHANIYU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Convert before paint, not after.&lt;/strong&gt; Ship the raw yuan value in the markup and let a blocking inline script do the arithmetic before anything is displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cena"&lt;/span&gt; &lt;span class="na"&gt;data-rmb=&lt;/span&gt;&lt;span class="s"&gt;"9.30"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;is:inline&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cm"&gt;/* до первой отрисовки: ничего не мигает, потому что нечему меняться */&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__KURS__&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mf"&gt;0.155507&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.cena[data-rmb]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rmb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; $&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An Astro-specific trap that cost us a day here: &lt;code&gt;&amp;lt;script is:inline&amp;gt;{ … }&amp;lt;/script&amp;gt;&lt;/code&gt; — with the body wrapped in braces — is not executed. Astro treats it as an expression and emits the code as text. It has to be a plain &lt;code&gt;&amp;lt;script is:inline&amp;gt;&lt;/code&gt; with the JavaScript directly inside. The page looks fine, the script is in the HTML, and nothing runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules we kept
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Take the rate from the institution your buyer's accountant will quote&lt;/strong&gt;, not from an aggregator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A pegged currency is arithmetic, not a feed.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the commercial markup and the FX buffer in separate variables&lt;/strong&gt;, even when they are both "just a multiplier".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refresh on a schedule, not per request.&lt;/strong&gt; Daily source, daily cadence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The default currency is the one most of the world reads&lt;/strong&gt;, because the default is what a buyer sees when detection is slow — and slow detection is the normal case on a cold mobile connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do the conversion before first paint.&lt;/strong&gt; A flicker between two currencies is read as a broken price, not as a loading state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our catalogue is public if you want to see the result under load — 385,341 listings, prices recomputed daily, at &lt;a href="https://opttoyschina.com/" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt;. The delivery-side of the same problem, where the inputs include a factory calendar that goes to zero for six weeks a year, is written up &lt;a href="https://opttoyschina.com/en/china-toy-production-calendar/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>javascript</category>
      <category>ecommerce</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Delivery dates are a data problem: 62 lanes, 385k SKUs and a calendar that zeroes production</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Sun, 20 Sep 2026 10:18:14 +0000</pubDate>
      <link>https://dev.to/opttoyschina/delivery-dates-are-a-data-problem-62-lanes-385k-skus-and-a-calendar-that-zeroes-production-1450</link>
      <guid>https://dev.to/opttoyschina/delivery-dates-are-a-data-problem-62-lanes-385k-skus-and-a-calendar-that-zeroes-production-1450</guid>
      <description>&lt;p&gt;We run a wholesale catalogue of 385,341 listings from 5,530 Chinese factories, and we ship to 62 countries. Every product page has to answer one question a buyer actually cares about: &lt;strong&gt;when will this be on my shelf?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a long time we treated that answer as a field. It is not a field. It is a pipeline with three independent inputs, and one of those inputs drops to zero for six weeks every year. This post is about how we model it, with the real numbers from our own data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive version, and why it breaks
&lt;/h2&gt;

&lt;p&gt;The obvious model is a lookup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dni&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;srokiPoStrane&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;strana&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;   &lt;span class="c1"&gt;// "18-40"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is wrong in three separate ways, and each one shows up as an angry email.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It assumes one transport mode. Air, rail and sea to the same country differ by a factor of five.&lt;/li&gt;
&lt;li&gt;It assumes the goods exist. Half a catalogue is stock, half is made to order.&lt;/li&gt;
&lt;li&gt;It assumes a factory is working today. For roughly six weeks a year, it is not.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Input one: the transit matrix
&lt;/h2&gt;

&lt;p&gt;Our matrix is 62 destinations × 3 modes. Not every cell is filled — and the empty cells carry as much information as the full ones.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Countries&lt;/th&gt;
&lt;th&gt;Air&lt;/th&gt;
&lt;th&gt;Rail&lt;/th&gt;
&lt;th&gt;Sea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Europe&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;5–9 d&lt;/td&gt;
&lt;td&gt;12–22 d (all 28)&lt;/td&gt;
&lt;td&gt;22–38 d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asia-Pacific&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1–9 d&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;2–30 d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middle East&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;4–8 d&lt;/td&gt;
&lt;td&gt;18–25 d (1 of 9)&lt;/td&gt;
&lt;td&gt;14–32 d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Africa&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;6–12 d&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;22–40 d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South America&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11–15 d&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;30–45 d&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;North America&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8–15 d&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;18–40 d&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rail exists for 29 of 62 destinations and for exactly one reason: continuous track from China. Every European destination has a rail figure; the Middle East has one (Iran); the Americas, Africa and the island parts of Asia-Pacific have none and never will. A &lt;code&gt;null&lt;/code&gt; in that column is not missing data — it is geography, and the code should say so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// null в колонке rail — это НЕ «данных нет», это «рельсов нет».&lt;/span&gt;
&lt;span class="c1"&gt;// Их нельзя дозаполнить позже и нельзя подставить среднее по региону.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dostupno&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;air&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sea&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rail&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[])];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you let a &lt;code&gt;null&lt;/code&gt; fall through to "unknown" your UI will eventually offer a train to Brazil.&lt;/p&gt;

&lt;p&gt;The widest spread is Asia-Pacific: sea from 2 days to 30. A single regional average would be useless for both ends of it. Aggregate for display, never for a promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input two: does the thing exist yet
&lt;/h2&gt;

&lt;p&gt;Across a 6,000-item sample of our live catalogue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;median price &lt;strong&gt;¥9.30&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;47% of items under ¥10, 90% under ¥50&lt;/li&gt;
&lt;li&gt;median MOQ &lt;strong&gt;72 pieces&lt;/strong&gt;, with the common cartons at 24 / 36 / 48 / 72 / 96 / 120&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those carton sizes matter to the date calculation more than the price does. A stocked carton ships this week. Anything below a carton does not ship at all — we do not break cartons — and anything above available stock becomes a production order with its own lead time of 15–30 days, stretching to 45 in peak season.&lt;/p&gt;

&lt;p&gt;So the second input is not a number, it is a branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proizvodstvo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poziciya&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kolichestvo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kolichestvo&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;poziciya&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moq&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;otkaz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ниже коробки&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kolichestvo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;poziciya&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ostatok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dney&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;          &lt;span class="c1"&gt;// со склада&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dney&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poziciya&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pik&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;              &lt;span class="c1"&gt;// под заказ&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Input three: the calendar that zeroes everything
&lt;/h2&gt;

&lt;p&gt;This is the one that generic logistics models miss, and it is the largest single term in the equation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chinese New Year 2027 falls on 6 February.&lt;/strong&gt; The official holiday is about a week. The real shutdown is roughly six weeks: factories wind down two to three weeks ahead as workers leave for their home provinces, and take three to four weeks afterwards to get back to full output, because a meaningful share of workers do not return to the same employer.&lt;/p&gt;

&lt;p&gt;There is a second, smaller one: &lt;strong&gt;Golden Week, 1–7 October&lt;/strong&gt;. Only a week, but it lands inside the run-up to the Western fourth quarter, when every hour of capacity is already booked. A week lost there cannot be made up.&lt;/p&gt;

&lt;p&gt;In code, the calendar is not an offset you add. It is a function that can make a date impossible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Календарь не «сдвигает» срок — он может сделать срок недостижимым.&lt;/span&gt;
&lt;span class="c1"&gt;// Это разные вещи, и ответ клиенту тоже разный: «позже» или «в этом сезоне никак».&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;skvozKalendar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dney&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;okna&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prostoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;   &lt;span class="c1"&gt;// CNY ± wind-down, Golden Week&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ostalos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dney&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ostalos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;vOkne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;okna&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;ostalos&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                &lt;span class="c1"&gt;// в простое счётчик не идёт&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical consequence, and the thing almost nobody acts on: for a small importer &lt;strong&gt;the cheapest available improvement is not a better price, it is ordering six weeks earlier.&lt;/strong&gt; It costs nothing and it moves you out of the queue. We wrote the full year-by-year breakdown up as a &lt;a href="https://opttoyschina.com/en/china-toy-production-calendar/" rel="noopener noreferrer"&gt;production calendar page&lt;/a&gt;, because it turned out to be the single question buyers asked most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together, backwards
&lt;/h2&gt;

&lt;p&gt;The only direction that works is backwards, from the shelf date to the order date. Four blocks, counted separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shelf date
  − marketplace check-in      up to 14 d
  − freight (mode, lane)       2–45 d
  − consolidation + QC          3–5 d
  − production                  0–45 d
  − calendar blackout           0–42 d
= order date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that for November 2026 and you get cut-offs that look absurdly early but are simply arithmetic: sea to the US had to leave by early September, rail and road to Europe by mid-September, air holds until mid-October at peak-season rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed in the code
&lt;/h2&gt;

&lt;p&gt;Three rules came out of this, and all three were bought with support tickets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never store a delivery date. Store the inputs and compute on read.&lt;/strong&gt; A stored date is a promise that silently rots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distinguish "no data" from "not possible".&lt;/strong&gt; Empty rail is not a gap in the dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let the calendar veto.&lt;/strong&gt; The function that returns a date must be allowed to return "not this season" instead of a later date, because those are different answers to the buyer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full transit table by country is public on our &lt;a href="https://opttoyschina.com/en/shipping-and-transit-times/" rel="noopener noreferrer"&gt;shipping and transit times page&lt;/a&gt;, and the live catalogue with prices and carton sizes is at &lt;a href="https://opttoyschina.com/" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt;. If you model delivery dates for physical goods and you have solved the calendar-veto problem more elegantly, I would genuinely like to read it.&lt;/p&gt;

</description>
      <category>data</category>
      <category>api</category>
      <category>ecommerce</category>
      <category>logistics</category>
    </item>
    <item>
      <title>Stale-while-revalidate on the edge: how our marketplace killed the cold start (and kept geo-currency)</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:11:33 +0000</pubDate>
      <link>https://dev.to/opttoyschina/stale-while-revalidate-on-the-edge-how-our-marketplace-killed-the-cold-start-and-kept-3do2</link>
      <guid>https://dev.to/opttoyschina/stale-while-revalidate-on-the-edge-how-our-marketplace-killed-the-cold-start-and-kept-3do2</guid>
      <description>&lt;p&gt;"Cold start" is not a law of nature. It's a caching strategy you haven't finished. Here's how our SSR marketplace serves a warm copy from ~300 datacenters — to every visitor, always, including the very first one after a deploy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Context: B2B toy marketplace, 389,000 products, mobile PageSpeed 93 on the full site. Receipts and the whole story &lt;a href="https://dev.to/opttoyschina/alibaba-scores-51-on-pagespeed-our-full-389000-product-marketplace-scores-93-the-dirty-secret-is-1h1n"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The two classic pains
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SSR pain:&lt;/strong&gt; while the page renders on the server, the user stares at a blank screen. &lt;strong&gt;Cache pain:&lt;/strong&gt; the copy expires once a day, and some unlucky visitor pays for the rebuild.&lt;/p&gt;

&lt;p&gt;Both die with one construct in the edge middleware — &lt;strong&gt;stale-while-revalidate&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request comes in → serve the cached copy &lt;strong&gt;instantly&lt;/strong&gt;, even if it's stale.&lt;/li&gt;
&lt;li&gt;In the background (&lt;code&gt;ctx.waitUntil&lt;/code&gt;), rebuild the page and store the fresh one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s-maxage=86400&lt;/code&gt; + Tiered Cache + Cache Reserve: the copy lives a day, regional PoPs pull from the upper tier, the reserve keeps a central fallback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nobody ever waits for a render. FCP is a stable 1.4s from any point on the globe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dilemma everyone solves wrong: personalization vs. caching
&lt;/h2&gt;

&lt;p&gt;A marketplace must show tenge in Almaty, som in Tashkent, dollars in Berlin. The usual answer — server-side geo rendering — means one cached copy per country, most of them permanently cold. Congratulations, your cache is decorative.&lt;/p&gt;

&lt;p&gt;Our answer: &lt;strong&gt;fully neutral SSR&lt;/strong&gt;. One HTML for the whole world → one cache → always hot. On the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the CDN already knows the geo — ask it for free&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/cdn-cgi/trace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// → country=KZ → sessionStorage&lt;/span&gt;
&lt;span class="c1"&gt;// prices in HTML are a neutral base; the client applies the local rate&lt;/span&gt;
&lt;span class="c1"&gt;// BEFORE prices paint — no flash of wrong currency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Personalization lives, and so does the cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  A living storefront that doesn't kill the API cache
&lt;/h2&gt;

&lt;p&gt;We wanted the homepage to rotate bestsellers every 5 minutes. Random offset per user = cache murder. Deterministic window = free liveliness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;   &lt;span class="c1"&gt;// every user on Earth gets the same slice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis serves it from memory; the storefront breathes at zero cost. (One follow-up rake: the window must respect your anti-bot depth cap — invariant: window ≤ min(pool, cap) — or the block silently starves once in a while.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveat
&lt;/h2&gt;

&lt;p&gt;1 PageSpeed run in 5 still catches a 77: the LCP poster on a rare ice-cold PoP. We inlined a 6 KB data-URI webp poster and deferred the 4 MB hero video past &lt;code&gt;window.load&lt;/code&gt; — the tail is almost dead, but not fully. We publish that number anyway; that's the difference between a measurement and marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify, don't trust
&lt;/h2&gt;

&lt;p&gt;Open DevTools on &lt;a href="https://opttoyschina.com/en/" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt; — every search request returns an open &lt;code&gt;Server-Timing&lt;/code&gt; header, and pasting the domain into pagespeed.web.dev takes 30 seconds. Questions on the middleware, cache keys, or the deterministic rotation — comments are open.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>performance</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Camera product search on the mobile web — the feature Alibaba and Amazon keep locked in their apps</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:10:16 +0000</pubDate>
      <link>https://dev.to/opttoyschina/camera-product-search-on-the-mobile-web-the-feature-alibaba-and-amazon-keep-locked-in-their-apps-fkk</link>
      <guid>https://dev.to/opttoyschina/camera-product-search-on-the-mobile-web-the-feature-alibaba-and-amazon-keep-locked-in-their-apps-fkk</guid>
      <description>&lt;p&gt;Camera-based product search has always been a flex reserved for the giants' &lt;strong&gt;native apps&lt;/strong&gt;: Alibaba has it in the app, Amazon has it in the app. On the mobile &lt;strong&gt;web&lt;/strong&gt;, among B2B marketplaces, we couldn't find it at anyone. So we shipped it on the website itself — and it costs our LCP exactly nothing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Context: B2B toy marketplace, 389,000 products from 5,530 factories. The full performance story with PageSpeed receipts is &lt;a href="https://dev.to/opttoyschina/alibaba-scores-51-on-pagespeed-our-full-389000-product-marketplace-scores-93-the-dirty-secret-is-1h1n"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The use case that sold us
&lt;/h2&gt;

&lt;p&gt;A wholesale buyer stands at a trade fair in Guangzhou — or in a competitor's shop — points a phone at a toy, and gets the product plus its analogs from our catalog: carton price, MOQ, freight to their hub. No app install, no account wall. One tap from the browser toolbar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client:&lt;/strong&gt; a deferred 50 KB camera modal (more on that below). Photo gets downscaled client-side before upload — nobody ships 12 MP over trade-fair Wi-Fi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoder:&lt;/strong&gt; SigLIP with a LoRA we fine-tuned on toy imagery, running on a rented RTX A4000 (~$0.085/hr). Encode: &lt;strong&gt;~38 ms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval:&lt;/strong&gt; kNN over &lt;strong&gt;886,219 image embeddings&lt;/strong&gt; — ~190 ms.&lt;/li&gt;
&lt;li&gt;End-to-end, camera click → results: &lt;strong&gt;~0.5 s&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why a LoRA? Generic CLIP-family models are decent at "a plush toy". Wholesale buyers need "THIS plush hippo, and the three factories making lookalikes". Fine-tuning on toy imagery moved retrieval from cute demo to daily tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The performance trick: the feature that isn't there
&lt;/h2&gt;

&lt;p&gt;The camera modal weighs 50 KB — and it is &lt;strong&gt;not in the critical path at all&lt;/strong&gt;. It loads as a deferred chunk after the first screen is painted. Lighthouse literally cannot see it. Our mobile PageSpeed stays 93 on the full 389k-product site, camera search included.&lt;/p&gt;

&lt;p&gt;That's the thesis we keep hammering: everything "only a native app can do" — done on the open web, indexed by Google, one link away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify, don't trust
&lt;/h2&gt;

&lt;p&gt;Every text-search request on the site returns an open &lt;code&gt;Server-Timing&lt;/code&gt; header — open DevTools on &lt;a href="https://opttoyschina.com/en/" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt; and watch the stages. Try the camera button from a phone. Questions about the LoRA training set, the embedding pipeline, or why we didn't use an off-the-shelf visual search API — comments are open.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>performance</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From 9,663 ms to 18 ms: sorting a 389k-product catalog in Postgres (no magic, three steps)</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:09:02 +0000</pubDate>
      <link>https://dev.to/opttoyschina/from-9663-ms-to-18-ms-sorting-a-389k-product-catalog-in-postgres-no-magic-three-steps-2342</link>
      <guid>https://dev.to/opttoyschina/from-9663-ms-to-18-ms-sorting-a-389k-product-catalog-in-postgres-no-magic-three-steps-2342</guid>
      <description>&lt;p&gt;Sorting a catalog by price took &lt;strong&gt;9,663 ms&lt;/strong&gt;. Same database, same hardware, three boring steps later: &lt;strong&gt;18 ms&lt;/strong&gt;. Here's the whole playbook — no exotic extensions, just Postgres used properly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Context: we run a B2B toy marketplace — 389,000 products from 5,530 factories, full story with PageSpeed receipts &lt;a href="https://dev.to/opttoyschina/alibaba-scores-51-on-pagespeed-our-full-389000-product-marketplace-scores-93-the-dirty-secret-is-1h1n"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The crime scene
&lt;/h2&gt;

&lt;p&gt;Classic marketplace schema drift: the price lived in a JSON field on the main products table, while all the storefront filters (category, stock, MOQ) hit a separate filter table. Every "sort by price" forced Postgres to join back, extract from JSONB, and sort 389k rows with nothing to lean on. EXPLAIN ANALYZE read like a horror novel: 9.6 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — denormalize into the table that does the WHEREs
&lt;/h2&gt;

&lt;p&gt;We copied &lt;code&gt;price&lt;/code&gt; and &lt;code&gt;moq&lt;/code&gt; into the filter table — the one already serving every storefront query. Yes, denormalization. No, it's not a sin: it's a trade you make consciously and document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — a trigger keeps the mirror honest
&lt;/h2&gt;

&lt;p&gt;One AFTER UPDATE trigger on the source table syncs the mirror columns. No cron drift, no "eventual consistency" surprises, no application-level sync bugs at 3 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — partial indexes for real query shapes
&lt;/h2&gt;

&lt;p&gt;We didn't index the whole table. We indexed what the storefront actually asks: in-stock rows per category ordered by price. Partial indexes are smaller, hotter in cache, and Postgres actually picks them.&lt;/p&gt;

&lt;p&gt;All migrations idempotent, built with &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; — zero downtime on a live store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Catalog sort: 9,663 ms → &lt;strong&gt;18 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;One factory's page (36,000 products from a single supplier): 2,270 ms → &lt;strong&gt;5 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Warm search over 389k products (FTS + trigram + pgvector cascade behind a Redis response cache): &lt;strong&gt;6–15 ms&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The iron rule that kept us honest
&lt;/h2&gt;

&lt;p&gt;Every optimization ran against a control set of queries, and &lt;strong&gt;the top-20 results had to be byte-identical before and after&lt;/strong&gt;. Speed bought by silently changing what customers see is not an optimization — it's a regression with good PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify, don't trust
&lt;/h2&gt;

&lt;p&gt;Our search returns an open &lt;code&gt;Server-Timing&lt;/code&gt; header on every request — open DevTools on &lt;a href="https://opttoyschina.com/en/" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt; and watch the stages yourself. Questions about the schema, the trigger, or the index shapes — comments are open.&lt;/p&gt;

</description>
      <category>database</category>
      <category>performance</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>Alibaba scores 51 on PageSpeed. Our full 389,000-product marketplace scores 93. The dirty secret is m-dot sites</title>
      <dc:creator>OPTTOYSCHINA</dc:creator>
      <pubDate>Fri, 17 Jul 2026 10:26:13 +0000</pubDate>
      <link>https://dev.to/opttoyschina/alibaba-scores-51-on-pagespeed-our-full-389000-product-marketplace-scores-93-the-dirty-secret-is-1h1n</link>
      <guid>https://dev.to/opttoyschina/alibaba-scores-51-on-pagespeed-our-full-389000-product-marketplace-scores-93-the-dirty-secret-is-1h1n</guid>
      <description>&lt;p&gt;&lt;em&gt;Every number below links to a permanent Google PageSpeed Insights report — click and verify.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;We build a B2B toy marketplace (389,000 products, 5,530 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.&lt;/p&gt;

&lt;p&gt;Turns out the suspicion is true. And it's funnier than we thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dirty secret: the m-dot
&lt;/h2&gt;

&lt;p&gt;Mobile PSI scores, homepages, July 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;What was actually measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IndiaMART&lt;/td&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;⚠️ m.indiamart.com — a stripped-down mobile shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;our marketplace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;93&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the full site, 389k products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Made-in-China&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;⚠️ m.made-in-china.com — same trick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ozon&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;full site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DHgate&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;full site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alibaba&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;full site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;full site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eBay&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;full site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlobalSources&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;full site&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice the pattern? Everyone above 70 — except us — serves Google's mobile agent &lt;strong&gt;a separate m-dot site&lt;/strong&gt;: 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?&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we deliberately did NOT build a native app
&lt;/h2&gt;

&lt;p&gt;Everyone told us to build an app. We refused — on purpose. Here's the math.&lt;/p&gt;

&lt;p&gt;An app is invisible to Google. Every user has to be &lt;strong&gt;bought&lt;/strong&gt;: 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.&lt;/p&gt;

&lt;p&gt;A fast full website is the opposite: &lt;strong&gt;all 389,000 product pages are indexed&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring architecture, fast results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Front&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back&lt;/strong&gt;: Python/FastAPI + Postgres + Redis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN&lt;/strong&gt;: Cloudflare.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing heroic. The speed came from discipline around three things: caching, the critical render path, and the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forever-warm site: nobody ever waits for a render
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;We killed both with one construct — SWR (stale-while-revalidate) in the edge middleware:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personalization vs. caching: the dilemma everyone solves wrong
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Our answer: &lt;strong&gt;fully neutral SSR&lt;/strong&gt;. One HTML for the whole world → one cache → always warm. On the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the CDN already knows the geo — ask it for free&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/cdn-cgi/trace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;KZ&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;sessionStorage&lt;/span&gt;
&lt;span class="c1"&gt;// prices in HTML are a neutral base; the client multiplies by the local rate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killing the PSI lottery
&lt;/h2&gt;

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

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

&lt;p&gt;&lt;strong&gt;Lottery #2 — the LCP element.&lt;/strong&gt; 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 &lt;strong&gt;inlined into the HTML as a 6 KB data-URI webp&lt;/strong&gt; — zero network dependency, it paints on the first frame even on an ice-cold PoP. The 4 MB globe video only starts after &lt;code&gt;window.load&lt;/code&gt; + idle, so it physically can't compete with the first screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML diet.&lt;/strong&gt; 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.)&lt;/p&gt;

&lt;p&gt;Result: mobile 93-93-93-93 and desktop &lt;strong&gt;99&lt;/strong&gt; (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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database: 389k products, 18 ms sorting
&lt;/h2&gt;

&lt;p&gt;Sorting the catalog by price took &lt;strong&gt;9,663 ms&lt;/strong&gt; — the price lived in a JSON field of the main table while filters lived in another. The classic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Denormalize price/moq into the filter table (the one already doing the WHEREs).&lt;/li&gt;
&lt;li&gt;A trigger keeps the mirror in sync.&lt;/li&gt;
&lt;li&gt;Partial indexes for the storefront's actual queries (idempotent migrations, CONCURRENTLY, zero downtime).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;9,663 ms → &lt;strong&gt;18 ms&lt;/strong&gt;. A giant factory page (36,000 products from one supplier): 2,270 ms → 5 ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search&lt;/strong&gt;: 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 &lt;strong&gt;6–15 ms&lt;/strong&gt;. Iron rule: the top-20 before and after any optimization must be identical on a control set — speed bought with relevance is a regression.&lt;/p&gt;

&lt;h2&gt;
  
  
  A living storefront that doesn't kill the cache
&lt;/h2&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;   &lt;span class="c1"&gt;// every user on Earth gets the same window&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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: &lt;code&gt;window ≤ min(pool, cap)&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond speed: photo search and AI search
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Search by photo — on the mobile WEB.&lt;/strong&gt; Here's the part we're genuinely proud of. Camera search has always been a privilege of the giants' &lt;strong&gt;native apps&lt;/strong&gt;: 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,530 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.&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  Speed is money (not our numbers — sourced)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deloitte × Google, &lt;a href="https://www.deloitte.com/ie/en/services/consulting/research/milliseconds-make-millions.html" rel="noopener noreferrer"&gt;"Milliseconds Make Millions"&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://web.dev/case-studies/milliseconds-make-millions" rel="noopener noreferrer"&gt;web.dev case study&lt;/a&gt;): a &lt;strong&gt;0.1s&lt;/strong&gt; mobile speed improvement lifts retail conversions by &lt;strong&gt;+8.4%&lt;/strong&gt; and average order value by +9.2%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google's &lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-new-industry-benchmarks/" rel="noopener noreferrer"&gt;industry benchmarks&lt;/a&gt;&lt;/strong&gt;: going from 1s to 3s load raises bounce probability by &lt;strong&gt;32%&lt;/strong&gt;; more than half of mobile users abandon slow pages.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rakes nobody blogs about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Double markup.&lt;/strong&gt; 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".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clobbering shared files.&lt;/strong&gt; 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".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bump the version or die.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The globe fell asleep forever.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iframe mobile emulation lies.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Receipts — permanent Google PSI reports (click and verify)
&lt;/h2&gt;

&lt;p&gt;Our runs (mobile, one-day series): &lt;a href="https://pagespeed.web.dev/analysis/https-opttoyschina-com/aza9t4hdjl?form_factor=mobile" rel="noopener noreferrer"&gt;run 1&lt;/a&gt; · &lt;a href="https://pagespeed.web.dev/analysis/https-opttoyschina-com/t7tm4dzm4w?form_factor=mobile" rel="noopener noreferrer"&gt;run 2&lt;/a&gt; · &lt;a href="https://pagespeed.web.dev/analysis/https-opttoyschina-com/lbdq66h3q1?form_factor=mobile" rel="noopener noreferrer"&gt;run 3&lt;/a&gt; · &lt;a href="https://pagespeed.web.dev/analysis/https-opttoyschina-com/zfqzm6f3a5?form_factor=mobile" rel="noopener noreferrer"&gt;run 4 — the honest floor&lt;/a&gt; · &lt;a href="https://pagespeed.web.dev/analysis/https-opttoyschina-com/vrwqh9649m?form_factor=desktop" rel="noopener noreferrer"&gt;run 5 + desktop 99&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Competitors (same day, same method): &lt;a href="https://pagespeed.web.dev/analysis/https-www-indiamart-com/x54zno5p26?form_factor=mobile" rel="noopener noreferrer"&gt;IndiaMART 98 — see the "Results for URL: m.indiamart.com" banner&lt;/a&gt; · &lt;a href="https://pagespeed.web.dev/analysis/https-www-made-in-china-com/hm3y3bwdo8?form_factor=mobile" rel="noopener noreferrer"&gt;Made-in-China 86 — same m-dot swap&lt;/a&gt; · &lt;a href="https://pagespeed.web.dev/analysis/https-www-alibaba-com/folqoshqef?form_factor=mobile" rel="noopener noreferrer"&gt;Alibaba 51 — full www&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Who we are: a small team behind *&lt;/em&gt;&lt;a href="https://opttoyschina.com" rel="noopener noreferrer"&gt;opttoyschina.com&lt;/a&gt;*&lt;em&gt;. The scale, in numbers: 389,000 products · 5,530 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 &lt;a href="https://pagespeed.web.dev" rel="noopener noreferrer"&gt;pagespeed.web.dev&lt;/a&gt; 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.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Update (Jul 21): several readers asked if we take on client work. We do now — a limited number of builds under **Prospects Digital&lt;/em&gt;&lt;em&gt;: full-site 90+ PageSpeed marketplaces, search that sells (including camera photo search on the mobile web), and speed rescues for existing stores. Same rule as this article — every claim ships with a verifiable Google report. → &lt;a href="https://opttoyschina.com/studio" rel="noopener noreferrer"&gt;opttoyschina.com/studio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>performance</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
