Full-stack React Native fashion app with GPU canvas product rendering, AI background removal, and Turborepo monorepo

My cousin runs a custom-products fashion shop. The category is personalised garments and accessories — items where the buyer chooses a design, colour, or embroidery before ordering. It is a real business with real inventory, real logistics, and an order volume that had grown past what a WhatsApp catalogue and manual spreadsheet tracking could handle. She needed a mobile storefront that could show the actual product, let the buyer customise it visually, process an Indian payment, and hand off to a courier — end to end, without her having to manually touch each step.
Family clients are their own category of complexity. The trust is immediate and the brief is vague. "Make it look good and make it easy to use" was, genuinely, the complete product specification for the first three conversations. The accountability runs both directions and does not follow invoice cycles. She can call me at 10pm about a checkout bug in a way that a corporate client cannot and would not. I cannot deliver something half-finished, invoice a second milestone, and move on. The person who is going to ask me about the status at the next Diwali dinner is also the client. You ship it completely or you don't ship it.
The platform is a Turborepo monorepo: 24,310 lines of TypeScript across 200 files. A React Native 0.79+ mobile app for buyers, a Next.js 15 admin dashboard for my cousin and her team, and an Express/Drizzle ORM API backend, all sharing types and utilities across workspace packages. Ten Docker services run on a single VPS. Solo developer, production-ready MVP.
The reason this app needed React Native Skia is the product customiser — and the reason it needed Skia specifically, not a JS-side canvas approximation, is low-end Android device performance.
The shop sells items where the buyer makes a visual choice before committing. They need to see the selected design rendered on the actual product image — composited together, not shown side by side — and be able to adjust the position and scale before adding to cart. The standard approach for this in React Native is to build the canvas in the JS thread and let React handle rendering. On a flagship device this looks fine. On the ₹8,000 Android phones that represent a significant portion of Indian e-commerce buyers, a canvas operation crossing the bridge at 60fps produces visible lag that breaks the illusion of direct manipulation. Users on those devices will abandon a customisation flow that feels unresponsive.
React Native Skia renders via the JSI on the UI thread, bypassing the bridge entirely. The customiser is a Skia canvas where the base product image and the user's selected design layer are composited in GPU memory. Reanimated 3 drives the pinch-to-zoom and drag-to-position gesture handling, also on the UI thread. The interaction is smooth on mid-range hardware because the rendering is never waiting on the JS thread. When the buyer confirms, the canvas output is encoded as a Blob and sent to the backend, where it is stored in MinIO as the preview image attached to the order — the operator can see exactly what the buyer approved.
The honest note: I also wanted to build something with Skia that was actually necessary, not a demo. Most Skia examples in the React Native ecosystem show particle animations or gradient backgrounds — things that are visually interesting but could be done other ways. Kanishka's customiser gave me a context where the GPU rendering was the correct solution, not a technology choice looking for a justification.
Before rembg, getting a new product photo onto the app meant: photograph the garment, open a background-removal app or pay a freelancer ₹20–50 per image, wait, download, re-upload. For a shop adding stock weekly that workflow is a recurring tax on whoever manages the admin panel. My cousin does not want to learn Photoshop and should not have to.
I added rembg as one of the ten Docker services. It is a Python library running a neural background-removal model locally — when a product image is uploaded through the Next.js admin panel, the API pipes it through rembg and stores both the original and the processed version in MinIO. The admin panel shows the processed version by default. No external API call, no per-image cost, no manual step, no dependency on a cloud service that could change pricing or availability.
The accuracy is very good for garments photographed against neutral or light backgrounds. It drops when the background is complex or the fabric edge is fine — chiffon, lace, and heavy embroidery on a busy background will sometimes need a manual correction. I told my cousin this directly: items shot against a white or pale wall process cleanly; items shot in context need a quick check before publishing. That is a real caveat. It eliminates the manual background step for around 90% of uploads and reduces the remaining 10% to a review rather than a full redo. The workflow improvement is real even with the edge case.
The self-hosting instinct more generally: I look at the default managed-service option for each component, check whether its cost and constraints fit the client's actual situation, and decide from there. For Kanishka's shop — a small Indian e-commerce business with a modest initial budget — Supabase cloud, Algolia, Cloudinary, and a managed Redis instance would collectively cost more per month than the VPS runs the entire ten-service stack. That is not a criticism of those services; they are good products and the right answer in other contexts. Defaulting to managed cloud for everything is not automatically optimal, and I did not want to build my cousin a platform with a monthly SaaS bill that would constrain her operating decisions before the business had scaled to support it.
Razorpay handles payments because it is the correct choice for Indian e-commerce: UPI, cards, net banking, and wallets in a single integration, with an SDK that has processed enough real Indian transaction volume to have the edge cases handled. Shiprocket connects to every major Indian courier — Blue Dart, Delhivery, Ekart, DTDC — and gives the admin panel a single dispatch view with tracking numbers and delivery confirmations, rather than my cousin logging into five courier portals to answer "where is my order" messages.
24,310 lines of TypeScript across 200 files. React Native mobile app, Next.js 15 admin panel, Express/Drizzle API, ten Docker services on a single VPS. Production-ready MVP delivered in March 2026.
Two honest caveats. The Meilisearch index needs to be kept warm on cold start — on a shared VPS the first search after a period of inactivity can take a couple of seconds while the index loads into memory. For the current traffic level this is acceptable; at growth it needs either a dedicated container or a pre-warming cron job. The MinIO bucket policies are permissive for development — read access is broader than it needs to be for a production store selling to the public. These are both documented second-pass items, not hidden problems. The schema is correct, the monorepo structure is correct, the API design is correct. The remaining hardening is a defined scope that I can return to in the next engagement.
The Turborepo monorepo structure means shared types between the mobile app and the backend — a product schema change propagates to both surfaces and fails at compile time in both if it breaks a contract, rather than failing silently at runtime. That discipline pays compounding interest as the codebase grows. It was the right architectural choice for a platform that is going to keep evolving as the shop does.
Forensic stabilisation and feature expansion of a production streaming platform across web, mobile, and TV
Full-stack React Native + Next.js travel app with Hono API, Prisma, MSW mock layer, and 68 commits in a week
OTT Platform — 9 Platforms, 1 Monorepo
Did this resonate?