Case Study — Hyperlocal Grocery / D2C Commerce
Client: Native Roots Retail Pvt. Ltd., Bengaluru
Nendran bananas at the right ripeness. Navara rice. Vettu cake. Kappa. The kind of catalog where a substitution isn't a substitution — it's a complaint.
We work on the software layer: two native apps, a web storefront, and the ordering, payments, membership and fulfilment logic underneath them. Getting the app to where it sits today involved fixing a rendering bug that was quietly costing the business orders for months, rebuilding the payment flow, and modelling a product catalog that no off-the-shelf commerce schema handles well.
4.7★
Play Store rating — 111 reviews
4.4★
App Store rating
10,000+
Installs
20+
Releases shipped

The common assumption is that a regional grocery app is a Blinkit clone with fewer SKUs. It isn't, and the reasons are structural.
Nendra Pazham, semi-ripe, 1 kg — “semi-ripe” isn't a description, it's a distinct purchasable variant with its own shelf life and handling. Ripeness is an attribute of the SKU. So is cut style for tapioca, and grind coarseness for masalas.
“Kappa,” “nendran,” “nenthran” — Roman-script Malayalam has no spelling authority. Text search fails invisibly: the user sees zero results, assumes the store doesn't stock it, and closes the app. Nobody files a bug for a search that returned nothing.
A failed payment the customer retries is a lost order — and at these basket sizes, not recoverable through a support call. Reliability here is a P&L issue, not an engineering-quality one.
A meaningful share of the user base is on mid-range Android hardware on patchy mobile data. Anything that looked fine on a test device in an office on Wi-Fi told us nothing.
The shape of the system is deliberately unremarkable, because the interesting problems weren't architectural. Three decisions are worth explaining — each had a live alternative we rejected.
The home screen composes catalog, promotions, serviceability, wallet and NestPass entitlement into one server-composed payload with partial-failure semantics — if promotions time out, the rest still renders. Rejected GraphQL: it solves over-fetching, which wasn't the problem, at the cost of a schema layer a weekly-shipping team didn't need.
The client sends coordinates; the server returns the serving dark store, delivery window, and catalog scope. Doing this on the client is cheaper right up until store coverage overlaps or an inventory exception changes the routing rule — and now a business rule needs an app release.
Shipped in v2.0.0. Any client below a floor version gets a blocking update prompt — unglamorous, and the single thing that made the rest of the roadmap possible: response shapes could change without maintaining compatibility with an app installed in 2024 and never updated.
In March and April 2025 the store reviews turned — users reported the app endlessly refreshing, the home screen blinking, unable to place an order. It wasn't an edge case: the app was effectively unusable for a segment of users, and had been for a while.
The home screen's data fetch was keyed on the resolved delivery location. Location resolution was asynchronous and could return more than once — a fast coarse fix, then a precise GPS fix, sometimes a third as accuracy improved. Each resolution wrote to location state. Each write invalidated the home query. Each invalidation triggered a refetch, which re-rendered the screen, which — because of how the location listener was mounted inside the rendered tree — re-subscribed the listener. On a device with weak GPS lock, this became a loop that only ended when the OS throttled it or the user gave up.
It reproduced almost never on good hardware with a fast lock, and reliably on a mid-range Android phone indoors. That asymmetry is why it survived so long.
Geolocation subscription lives in a single app-level provider, mounted once, never remounted by a screen.
Location updates are debounced and only promoted to “delivery location” once the fix stabilises — or the user explicitly picks an address. Coarse fixes render optimistically without triggering a catalog refetch.
Home content is keyed on the serving dark store, not raw coordinates. Moving eleven metres no longer invalidates anything.
Shipped in v2.1.0. The blank-then-populate flash was itself being read as “blinking” — reserving layout space removed the perceived flicker along with the real one.
A development-only assertion that fails the build if a component exceeds a render-count threshold in a fixed window. Cheap, and it has caught two regressions since.
What we'd do differently: low-end Android should have been in the test matrix from day one, and store reviews should have been read as a monitoring signal rather than marketing feedback. Two users described the exact failure mode in plain language weeks before it was formally triaged. That's a process failure, not a code one.
Off-the-shelf commerce schemas assume brand + product + variant, differing on size or colour. That breaks immediately here. We restructured around a product concept with typed attribute sets that vary by category — produce carries ripeness stage and origin, provisions carry grind and roast level, bakery carries a same-day flag that drives slot eligibility.
Sold-by-weight items needed the most care. A customer orders 1 kg of nendran; what gets picked is 1.06 kg. We reconcile, with a stated tolerance band and a final bill reflecting actual weight — the alternative silently loses margin on every produce order, and at this scale that compounds fast.
Malayalam terms and their common Roman spellings — kappa → tapioca, and the four spellings of nendran people actually type.
For names outside the map, tuned for Malayalam phoneme clusters rather than the English defaults most analysers ship with.
A first-class product signal, reviewed weekly. Every empty search is a missing alias, a missing synonym, or a genuine stocking opportunity — one of the highest-value telemetry decisions in the project.

Any UPI-based flow has the payment happen outside your app — the user leaves for the PSP, and control comes back through a redirect that may not arrive. The old flow treated that redirect as the source of truth, which produces the worst possible failure: the money moves and the order doesn't exist.
The client's return from the PSP is a hint to check, never a confirmation. Order state advances on verified server-side status, full stop.
A retry from an impatient user cannot produce a second order or a second charge.
Sweeps pending intents on a schedule and resolves them against the PSP, so an order stranded by a dropped callback settles itself instead of waiting for a support ticket.
“Payment processing” is a real state with a real UI, not a spinner that times out into ambiguity. Users tolerate waiting. They don't tolerate not knowing whether they've been charged.
Checkout screenshot pending: UPI flow including the “payment processing” state.
Bangalore addressing is genuinely hard — layouts have informal names, buildings share numbers, and a pin drop is often more reliable than anything typed. We rebuilt address capture around map-first entry: the user confirms a pin, then annotates it with flat number and landmark, rather than filling a form.
Sets catalog scope and the delivery promise.
Before the customer invests time in a basket.
Coverage and inventory can change between browsing and paying.
Checking only once and trusting it is how customers end up with a cart they cannot buy.
We measure on a defined low-end Android baseline, throttled, on cold cache. Numbers from a flagship on office Wi-Fi are decorative.
Membership is deceptively complex because it touches pricing, and pricing touches everything. A NestPass holder sees different prices, different delivery fees, and member-only products. Charge a member full price and you have a refund and a churned customer; show member pricing to a non-member and you lose margin silently.
The clients never compute price. They display what they're told.
Entitlement resolution is a single server-side service that returns an effective price and fee for a given user and basket. Every bug we've seen in membership systems elsewhere traces back to price logic living in more than one place.
Stacking rules — coupon plus NestCash plus member price plus flash discount — are expressed as an ordered, explicit ruleset rather than accumulated conditionals, so the finance team can read the rules without reading code.

The fastest way to damage a client's business is to put a model somewhere a lookup table would have served better.
Semantic matching as a fallback tier behind the alias map and phonetic layer, not in front of it. Deterministic matching runs first because it's faster, cheaper and auditable.
Store reviews and support messages routed into structured categories — app defect, delivery, packaging, product quality, supplier-specific quality. Came directly from watching the flicker bug sit in plain sight in the reviews.
Bakery and produce with short shelf life carry the worst waste economics in the catalog. Forecasting at SKU × dark store × day-part, used as a purchasing aid, not an automated ordering system.
Users of a grocery app want to reorder, not converse. Reorder-from-history and Favourites do the same job with none of the latency or failure modes.
Technically straightforward, commercially wrong here — this customer base has strong opinions about what's an acceptable substitute for a specific rice. We surface options and let them choose.
Trust in regional grocery is built slowly and lost in one screenshot shared to a WhatsApp group.
Twenty-plus releases across the engagement, with a visible shift partway through: from feature-tagged builds, to consolidated versioned releases with real release notes, a force-update floor, and staged rollout on Android — the flicker regression is exactly the class of bug staged rollout catches at 5% instead of 100%.
Case studies that only contain wins are advertisements.
We tested on the wrong devices. The flicker bug was device-class-specific and our test matrix didn't include the device class most of the user base owns. Every subsequent perf commitment is now measured on a low-end baseline because of this.
We treated store reviews as marketing. Two users described a critical defect precisely, in public, before it was triaged. Reviews are now ingested and classified automatically. It took a real incident to make that obvious.
The web storefront is still behind the apps. nativenest.in is a client-rendered SPA; every route serves the same static meta description, so product pages have effectively no organic search presence — for a business whose customers actively search “Kerala grocery Bangalore.” Server-side rendering for product and category routes is the highest-ROI item currently unbuilt.
We took an existing, live product with real customers and real revenue, found the failures that were costing orders, and fixed them in an order determined by business impact rather than engineering interest. Then we built on top of it. If you have a product in market that isn't performing the way it should — and you suspect the reasons are technical but can't prove it — that's the work we do.
Book a free discovery call and let's explore how Dev can accelerate your business with AI-first solutions.
🍪 We use cookies
We use cookies to analyze site traffic and improve your experience. No personal data is sold.