The catalogue outgrew the frontend rendering it
A hyperlocal SaaS marketplace across Europe, with a shopping path rebuilt around the slowest phone its shoppers own. Here is what changed and what it does not prove.
The catalogue had grown faster than the frontend that rendered it. Carts were slow on the mid-range phones most shoppers used. Each new city made that worse rather than better.
Buying more server capacity did not help. The work that was slow was happening on the phone.
Three things changed. Server-side rendering moved the first paint off the device. The client state layer was rebuilt, so a large catalogue no longer meant a large memory footprint. City onboarding became a configuration step instead of a rebuild.
We do not name clients in published case studies. A case study has to disclose how a system was built and where it strained. That detail can be traced back to a named company. So the name stays out here, and yours would stay out on the same terms.
What we can put in front of you instead is the method and the mechanism. There is also a section that states what the result does not establish.
Everything below is one of two things. It is either work we did on this engagement, or a published figure from a named source about the category. The two are kept apart on purpose, and each one says which it is.

When the phone throws the storefront away
On a phone that is short of memory, the browser is allowed to unload a page it is still showing.
The page enters what it calls the discarded state. Discards happen under resource pressure, and the page is unloaded to conserve resources.
The costly part is the next line of that same documentation.
A shopper sees the storefront sitting in the tab strip. They touch it, and the whole thing reloads from the start.
That is also why your dashboards may be quiet about it.
A discarded page runs no JavaScript. So it cannot fire the event that would tell your analytics anything happened.
The question to put to your own storefront is not only how many seconds it takes to appear.
Support tickets that say the app logged me out, or my basket emptied itself, are often this and get filed as something else.
Chrome for Developers documents this in its Page Lifecycle API reference. It is unloaded to conserve resources, and no tasks, no event callbacks and no JavaScript of any kind can run in it.
The tab still appears present. A cart part-way through and a scroll position twelve screens down both go. Nothing on screen explains why.
One session ends, another begins a moment later, and joining the two after the fact is difficult.
Memory pressure is not exotic on this hardware. The Chromium Projects say so in their own memory documentation for Chromium 126. A large fraction of process crashes in Chromium are due to out-of-memory conditions. Most Android ARM64 systems have only 40 bits of address space. That is Chromium's wording, and there is no percentage behind it to quote.
It is how much the browser is being asked to hold while somebody shops.
Here is why more server capacity does not move this. Alex Russell names the device at the 75th percentile of the global population as a Samsung Galaxy A24 4G, in The Performance Inequality Gap, 2026, published on Infrequently Noted in November 2025. He measures the low-end and mid-tier classes against the same reference. Low-end single-core performance is nine times slower than contemporary iPhones. Mid-tier devices remain more than three and a half times slower.
Separately, from our own record: the constraint on this engagement was low-memory mobile devices, the mid-range phones most shoppers used. That is a memory constraint. The chart below measures processor speed, which is a different axis.
Show data table
| Item | times slower than a contemporary iPhone |
|---|---|
| Contemporary iPhone | 1 x |
| Mid-tier phone (at least) | 3.5 x |
| Low-end device | 9 x |
We measured on the devices the shoppers owned, not on a development laptop. We also chose the percentile before measuring anything. A performance figure with neither of those attached cannot be read, and that includes ours.
For scale, look at the spread the category reports. In lab runs, the HTTP Archive Web Almanac 2024 puts Total Blocking Time on mobile at 1,208 ms at the median, 2,988 ms at the 75th percentile and 5,950 ms at the 90th. On desktop the same measure comes in at 67 ms, 282 ms and 718 ms. Same web, two different products.
Show data table
| Dimension | Mobile | Desktop |
|---|---|---|
| p50 | 1,208 ms | 67 ms |
| p75 | 2,988 ms | 282 ms |
| p90 | 5,950 ms | 718 ms |
What server rendering moves off the phone
Building a page in the browser
Building a page in the browser costs single-threaded CPU time. Tiles and prices have to be constructed before any of it can be seen. On a fast laptop that work is invisible.
Server-side rendering takes the first cost away
The server produces the markup for the first screen. The phone receives something it can paint straight away. The shopper sees a storefront instead of an empty frame.
The half left out of the pitch
A server-rendered page can look completely loaded and still refuse to answer a tap. Jason Miller and Addy Osmani describe this in Rendering on the Web on web.dev.
In practice it looks like this
A shopper taps a category. Nothing happens. They tap it again, and now two taps are queued against a page that has just woken up.
First paint and nothing past it
That is why the state work in the next section was not optional. The heavier the client state layer, the longer that unresponsive stretch runs.
That is the slowest resource a mid-range phone has. On the phone in a shopper's hand it is most of the wait.
A server-rendered page can appear loaded and interactive, and still not respond to input. It stays unresponsive until the client-side scripts for its components have run and their event handlers are attached. The source puts no number on that gap, and neither will we.
Rendering on the server buys the first paint and nothing past it. The storefront looks ready and ignores the person using it.
Four layers carry a storefront like this one. The server render. The transport between server and phone. The client state layer. The view a shopper touches. The change on this engagement sat at one boundary, between state and view.
Google's own guidance on web.dev says something adjacent. In How large DOM sizes affect interactivity, it names large node sets held by general query selectors as a memory risk. That guidance is qualitative, and we are leaving it qualitative.
There is a test in this for your own system and it takes an afternoon. Load a small category on a mid-range phone and take a heap snapshot. Load a much larger category and take another one. If the second is meaningfully bigger, your state layer is tracking the catalogue. It will keep doing that as you grow, and no amount of server capacity will change it.
The layers of the running system
Layer 1
Server render
Building a page in the browser costs single-threaded CPU time. The server produces the markup for the first screen.
Hands upward
The phone receives something it can paint straight away. The shopper sees a storefront instead of an empty frame.
Layer 2
Transport
The transport between server and phone, which also carries some of the view logic in places.
Hands upward
It stays unresponsive until the client-side scripts for its components have run and their event handlers are attached.
Layer 3
Client state
Before, the client state layer grew with the catalogue. Every city added to the same in-memory pile.
Hands upward
After the rebuild, the state layer holds what the current view needs. The footprint stops tracking catalogue size.
Layer 4
View
The view a shopper touches. A re-render touches what changed rather than walking the whole list.
Hands upward
The amount the browser had to hold went up as the business succeeded. That is the shape that turns growth into a mobile problem.
The lines drawn here are cleaner than the code. State and view still share ownership in places. Any diagram of a running system is a simplification we chose, not a boundary the code enforces.
What made this hard, ranked
1
Holding as the catalogue grows
A storefront that is fast at today's catalogue size and slow at twice it has not been fixed, only tuned.
- Why 2 waits for 1Ahead of the device ceiling, because a fix with an expiry date gets paid for twice.
2
The slowest phone sets the ceiling
Nothing bought on the server side raises it. More server capacity and a faster database do not change how long that chip takes to build a page.
- Why 3 waits for 2Ahead of the paint-versus-tap problem, because the device ceiling decides how wide that gap is to begin with.
3
Paint and tap are separate problems
Moving rendering to the server settles one of them. The other depends on how much work the client still has to do before it will answer.
Ranked by which one bit hardest, not by the order they were tackled. If you would put the device ceiling first, say so when we speak.
That ordering changes where the work starts.
Every decision was checked against a catalogue larger than the one in front of us.
That is why the state layer was rebuilt rather than trimmed.
The order the work had to happen in
Four steps, each one held in place by the one before it. Start anywhere else and you will redo it.
- Measure on the phones the shoppers hold, because every later decision is graded against that number
Name the device class and the percentile before anything is measured. A figure taken on a development laptop cannot tell you whether the person shopping is better off.
- Move the first paint to the server, because the phone cannot build the first screen fast enough to be worth waiting for
This is the largest single cost taken off the device. It also brings the remaining problem into view instead of leaving it buried under the first one.
- Rebuild the state layer, because step two on its own leaves a storefront that looks ready and is not
Bounded memory for the client state. The browser has less reason to discard the page, and the wait between looking ready and answering a tap gets shorter.
- Make city onboarding a configuration step, because a fix that only holds at one catalogue size is not a fix
That is what keeps step three true as the catalogue keeps growing, and it is the step teams skip when they are in a hurry.
It usually says the opposite of the truth.
The footprint stops following catalogue size.
Adding a city stops being a rebuild.
What we can evidence
Atyantik engagement record for this marketplace
Atyantik engagement record for this marketplace
Atyantik engagement record for this marketplace
*Disclaimer: Three facts, in the wording our own record uses. The third is a direction rather than a number. No latency figure for this work is published, and we are not going to invent one.
The boundary on each number
| The figure | What it supports | Where it stops |
|---|---|---|
| 1000+ shops onboarded. | What it supportsOnboarding volume across the engagement. | Where it stopsSays nothing about retention, or about how many are trading today. |
| 4+ expanded to cities. | What it supportsThat the expansion happened. | Where it stopsSays nothing about profitability, volume per city, or how long it took. |
| Faster load on low-memory devices. | What it supportsA direction on a named device class. | Where it stopsThere is no latency figure behind it, and no revenue or order outcome. |
| The two charts above. | What it supportsWhat the category looks like in published data. | Where it stopsNeither one measures this client's system. |
| One engagement. | What it supportsThat this happened once. | Where it stopsIt is not evidence of a typical outcome anywhere else. |
Where this does not transfer
Where this transfers
It does transfer when three things are true at once. Most of the buying happens on mid-range phones.
The catalogue keeps growing, whether by cities or by sellers.
And somebody is willing to measure on the device class the shoppers hold, rather than on the laptop the team works on.
Where this does not transfer
It does not transfer if the storefront is slow because of the database or the API.
It does not transfer if the catalogue is small enough for the client to hold.
It does not transfer if most of the buying happens on desktop.
If your server response times are the problem, everything above is the wrong repair. Finding that out the expensive way takes a long detour. Start with your slowest queries instead.
Bounded state costs real complexity. A few hundred items on a modern phone is not a memory problem. Leave it alone.
The device ceiling that drove every decision here is a mobile ceiling. On a desktop-majority storefront the same work would very likely change nothing worth paying for.
That third condition is the one that usually fails. It is also the cheapest of the three to fix, and it can be settled this week with one phone and one afternoon.
If all three are true, the next thing worth looking at is how work of this kind gets scoped before anybody writes code.
How we scope performance workTalk to us about your own storefront
If your storefront is slow on the phones your shoppers hold, start with a conversation about that specific system. Bring what you have. Where the traffic comes from. What the catalogue looks like now, and what it looks like once the plan works. Any numbers you already hold, including the ones you do not trust.
Two outcomes here are good ones. Either we take the work, or we tell you the constraint sits somewhere we would not be the right people to fix. In the second case you leave with that answer and the reasoning behind it. It happens often enough that we plan for it.
On checking us out. We do not print client names or client quotes, for the confidentiality reason set out at the start. What we do instead is put references on a call, under the same terms that keep names out of our published work. Ask for that when we speak and we will arrange it.
One thing we will want from you before we say anything about approach. A single measurement taken on a real device from your own traffic. Without it, any recommendation would be a guess, and you have probably been sold one of those before.

- We do not print client names or client quotes.
- References go on a call, under the same terms that keep names out of our published work.
- Either we take the work, or we tell you the constraint sits somewhere we would not be the right people to fix.
- Bring a single measurement taken on a real device from your own traffic.