Case study · Marketplace

A rental marketplace, and the weight we took out of it

Rentlelo is a peer-to-peer rental marketplace: listings with photos, search and filters, bookings, a coupon and wallet system, time-limited offers, and a refund ledger. It runs server-rendered under PM2 behind Nginx.

From 1,208 KB to 758 KB

Mobile Lighthouse went from 74 to 89 and largest contentful paint from 5.2 seconds to 3.3. The wins were unglamorous and repeatable.

Fonts were being pulled through an @import, which blocks rendering; self-hosting the variable files removed that entirely. Every image was being served from external photo hosts, so we localised the lot. And the rupee symbol turned out to be dragging in an 84 KB Latin-Extended font subset on its own — split into a dedicated 8.6 KB currency face, that one character stopped costing more than the rest of the typography.

MetricBeforeAfter
Mobile Lighthouse7489
Largest contentful paint5.2 s3.3 s
Page weight1,208 KB758 KB

Promises the code has to keep

The published refund policy said full refund until pickup. There was no refund code anywhere in the system. A policy page that the software cannot honour is not a marketing problem, it is a liability, so we built a refund ledger that raises a pending refund automatically when a paid booking is cancelled — idempotent, and never able to block the cancellation itself.

We also removed a promise we could not keep. The policy offered refunds to an in-app wallet; the wallet holds loyalty coins, not rupees, and a currency wallet in India is a regulated payment instrument. Refunds are now stated as returning to the original payment method, which is both accurate and legal.

Similarly, advertised discounts are applied server-side in the booking service, so the price a customer is shown is arithmetically the price they are charged. Time-limited offers expire in the SQL query rather than relying on a sweep job that might not run.

Interface bugs that only appear on real devices

A carousel died permanently after any scroll that began on it. Touch-start paused the autoplay and touch-end resumed it — but browsers fire touch-cancel, not touch-end, when a scroll takes over the gesture. We hit the identical bug class twice in the same codebase before adopting a rule: always bind touch-cancel as a resume path.

Cards inside horizontal rails were permanently invisible, because a scroll-reveal observer uses the viewport as its root — so a card parked off-screen to the right never intersects and never gets revealed.

And an apparent "the animation is missing on mobile" report turned out to be reduced-motion switching off every guarded animation at once. It looks exactly like a failed deploy, which is why it is now the first thing we check.

Related

Want this built?

Tell us what you are trying to ship and we will tell you honestly whether we are the right team for it.