Portfolio

    Case Study — Hyperlocal Grocery / D2C Commerce

    NativeNest — a Kerala grocery commerce platform for Bangalore

    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.

    Sector: Hyperlocal grocery / D2C
    iOS
    Android
    Responsive web

    At a glance

    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

    NativeNest home screen — promo carousel, Special Offer row and New arrivals
    The home screen. Promo carousel up top, then Special Offer and New rows — the same screen the flicker bug made unusable for a segment of users before the fix.

    Why this was harder than "build a grocery app"

    The common assumption is that a regional grocery app is a Blinkit clone with fewer SKUs. It isn't, and the reasons are structural.

    The catalog isn't standardised

    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.

    Customers search in two languages at once

    “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.

    The margins don't allow slack

    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.

    The buyer's phone isn't your phone

    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.

    Architecture

    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.

    A backend-for-frontend, not direct calls

    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.

    Serviceability resolved server-side, cached

    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.

    Versioned API contracts, force-update floor

    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.

    iOS appAndroid appWeb storefrontAPI gateway / BFFCatalog & searchserviceability, geo routingOrders & fulfilmentdark-store allocationPaymentsPhonePeIdentity & walletNestCash · NestPass

    The home screen that wouldn't stop blinking

    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.

    The fix, in order of importance

    1

    Move location out of the render tree

    Geolocation subscription lives in a single app-level provider, mounted once, never remounted by a screen.

    2

    Debounce and settle

    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.

    3

    Make the query key coarse

    Home content is keyed on the serving dark store, not raw coordinates. Moving eleven metres no longer invalidates anything.

    4

    Skeleton loaders instead of empty states

    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.

    5

    A render-loop guard in staging

    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.

    Before — the loopLocation fixarrivesState writeQuery invalidatedRefetch +re-renderlistener re-subscribes → loopsAfter — settlesDebounced fix,mounted oncePromoted onlywhen stableQuery keyedon dark storeRenders once

    Rebuilding the catalog and search

    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.

    Curated alias map

    Malayalam terms and their common Roman spellings — kappa → tapioca, and the four spellings of nendran people actually type.

    Phonetic fallback

    For names outside the map, tuned for Malayalam phoneme clusters rather than the English defaults most analysers ship with.

    Zero-result logging

    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.

    NativeNest category grid — Onam Special, DryFish, Ayurvedic Medicine and other Kerala-specific categories
    The category structure this catalog actually needs — Onam Special as a seasonal category, DryFish and Ayurvedic Medicine as first-class sections no generic grocery taxonomy ships with by default.

    Payments

    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.

    Server-side status reconciliation is authoritative

    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.

    Idempotency keys on order creation

    A retry from an impatient user cannot produce a second order or a second charge.

    A reconciliation job

    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.

    Honest client states

    “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.

    Location, addresses, and serviceability

    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.

    1

    On app open

    Sets catalog scope and the delivery promise.

    2

    On address selection

    Before the customer invests time in a basket.

    3

    At checkout

    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.

    Performance on the phones people actually own

    We measure on a defined low-end Android baseline, throttled, on cold cache. Numbers from a flagship on office Wi-Fi are decorative.

    • Responsive image variants served at the size actually rendered — full-resolution images were being downloaded for thumbnail slots.
    • List virtualisation with stable keys — category listings were mounting every row of a 400-item list.
    • Cursor pagination on history surfaces — offset pagination re-scanned growing result sets and duplicated rows mid-scroll.
    • Optimistic cart with server reconciliation — mutations apply locally and reconcile after, instead of every add-to-cart waiting on a round trip.
    • Skeleton loaders — as much a perceived-performance fix as a real one.

    The loyalty stack: NestCash, wallet, coupons, NestPass

    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.

    NativeNest cart drawer showing member pricing on every line item
    Member pricing shown on every cart line — the crown badge and “Get for ₹X” — resolved once server-side and displayed, never computed on the client, same principle as the pricing entitlement service described above.

    Where AI earned its place, and where we cut it

    The fastest way to damage a client's business is to put a model somewhere a lookup table would have served better.

    Shipped / in progress

    Zero-result search recovery

    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.

    Review and complaint classification

    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.

    Demand signals for perishables

    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.

    Cut, deliberately

    Conversational shopping assistant

    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.

    Automatic substitution

    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.

    Dynamic pricing

    Trust in regional grocery is built slowly and lost in one screenshot shared to a WhatsApp group.

    Release discipline

    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%.

    v0.1Sep 2024Wallet, NestCash, coupons sh…v2.0.0PhonePe rebuild, versioned A…v2.1.0Performance release, skeleto…v17.0Jun 2025NestPass membership launches

    What we'd do differently

    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.

    Ready to Get Started?

    Book a free discovery call and let's explore how Dev can accelerate your business with AI-first solutions.

    Get Dev logo

    © 2026 Get Dev. All rights reserved.

    🍪 We use cookies

    We use cookies to analyze site traffic and improve your experience. No personal data is sold.